from django.utils import timezone
from django.contrib.auth.models import User, Group
from rest_framework import viewsets, permissions, status
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.authtoken.models import Token
import paramiko
import socket
import logging
import os
import json
import psutil
import stat
import zipfile
import tempfile
from datetime import datetime, timedelta
from django.http import FileResponse
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models import Count, Q
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
import io
import threading
from django.views.generic import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
import time
import subprocess

# 初始化日志记录器
logger = logging.getLogger(__name__)

from .models import (
    Host, HostGroup, HostType, ConnectionRecord, CommandLog, Alert, UserProfile,
    UserGroup, UserRole, UserGroupRole, MenuItem, GroupMenuPermission,
    SSHSession, SSHCommand, SSHReplayData, Role, GroupRole, RoleMenuPermission
)
from .serializers import (
    UserSerializer,
    RegisterSerializer,
    HostSerializer,
    HostGroupSerializer,
    HostTypeSerializer,
    ConnectionRecordSerializer,
    CommandLogSerializer,
    AlertSerializer,
    UserProfileSerializer,
    ChangePasswordSerializer,
    UserGroupSerializer,
    UserRoleSerializer,
    UserGroupRoleSerializer,
    UserWithGroupsSerializer,
    SimpleUserRoleSerializer,
    MenuItemSerializer,
    GroupMenuPermissionSerializer,
    GroupSerializer,
    RoleSerializer,
    GroupRoleSerializer,
    RoleMenuPermissionSerializer,
    MenuPermissionTreeSerializer,
    GroupWithRolesSerializer,
    DynamicMenuItemSerializer,
    MenuCreateSerializer
)
# ansible_utils已移除，相关功能已迁移到ansible_management应用
from .tasks import check_single_host, auto_detect_host_info


class RegisterView(APIView):
    """用户注册视图"""
    permission_classes = [permissions.AllowAny]

    def post(self, request):
        print(f"接收到注册请求，数据: {request.data}")
        serializer = RegisterSerializer(data=request.data)
        if serializer.is_valid():
            user = serializer.save()
            print(f"用户创建成功: {user.username}, ID: {user.id}")

            # 确保UserProfile已创建
            if not hasattr(user, 'profile'):
                from .models import UserProfile
                UserProfile.objects.create(user=user)
                print(f"为用户 {user.username} 创建了新的 UserProfile")

            # 设置中文名（如果提供）
            chinese_name = request.data.get('chinese_name')
            if chinese_name:
                user.profile.chinese_name = chinese_name
                user.profile.save()
                print(f"为用户 {user.username} 设置了中文名: {chinese_name}")

            # 创建token
            token, created = Token.objects.get_or_create(user=user)
            print(f"为用户 {user.username} 创建了 Token: {token.key}")

            # 返回用户信息和token
            return Response({
                'user': UserSerializer(user).data,
                'token': token.key
            }, status=status.HTTP_201_CREATED)
        print(f"注册验证失败，错误: {serializer.errors}")
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class UserViewSet(viewsets.ModelViewSet):
    """用户视图集"""
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]  # 设置为所有已登录用户

    def get_permissions(self):
        """根据操作类型设置权限"""
        # 将所有操作权限统一设置为已登录用户
        permission_classes = [permissions.IsAuthenticated]
        return [permission() for permission in permission_classes]

    def get_serializer_context(self):
        """确保序列化器上下文中包含请求对象"""
        context = super().get_serializer_context()
        context.update({'request': self.request})
        return context

    def create(self, request, *args, **kwargs):
        """创建用户，处理密码和用户资料"""
        # 提取profile相关字段
        chinese_name = request.data.get('chinese_name')
        groups = request.data.get('groups', [])

        # 从请求数据中创建序列化器数据
        serializer_data = request.data.copy()

        # 如果有chinese_name，转换为正确的嵌套格式
        if chinese_name:
            # 移除顶层的chinese_name
            if 'chinese_name' in serializer_data:
                serializer_data.pop('chinese_name')

            # 添加嵌套的profile数据
            serializer_data['profile'] = {'chinese_name': chinese_name}

        serializer = self.get_serializer(data=serializer_data)
        serializer.is_valid(raise_exception=True)

        # 提取并移除密码 (密码在序列化器中不可见)
        password = request.data.get('password')
        if not password:
            return Response(
                {"error": "密码不能为空"},
                status=status.HTTP_400_BAD_REQUEST
            )

        # 创建用户实例
        user = serializer.save()

        # 设置密码
        user.set_password(password)
        user.save()

        # 确保用户资料存在
        if not hasattr(user, 'profile'):
            UserProfile.objects.create(user=user)

        # 如果有中文名但没有通过序列化器设置，直接在profile中设置
        if chinese_name and not user.profile.chinese_name:
            user.profile.chinese_name = chinese_name
            user.profile.save()

        # 处理用户组关联
        if groups:
            self._handle_user_groups(user, groups)

        # 返回创建的用户
        headers = self.get_success_headers(serializer.data)
        return Response(
            serializer.data,
            status=status.HTTP_201_CREATED,
            headers=headers
        )

    def update(self, request, *args, **kwargs):
        """更新用户，处理用户资料"""
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        groups = request.data.get('groups', [])

        # 确保用户资料存在
        if not hasattr(instance, 'profile'):
            UserProfile.objects.create(user=instance)

        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)

        # 保存用户更新
        self.perform_update(serializer)

        # 处理用户组关联
        if 'groups' in request.data:
            self._handle_user_groups(instance, groups)

        return Response(serializer.data)

    def _handle_user_groups(self, user, group_ids):
        """处理用户组关联"""
        from django.contrib.auth.models import Group

        # 清除现有的Django原生组关联
        user.groups.clear()

        # 清除现有的自定义用户组角色关联
        UserGroupRole.objects.filter(user=user).delete()

        # 添加新的组关联
        for group_id in group_ids:
            try:
                # 添加到Django原生组
                django_group = Group.objects.get(id=group_id)
                user.groups.add(django_group)

                # 尝试找到对应的自定义用户组
                try:
                    custom_group = UserGroup.objects.get(id=group_id)
                    # 为该组找一个默认角色
                    default_role = UserRole.objects.filter(group=custom_group, level=1).first()
                    if default_role:
                        UserGroupRole.objects.create(
                            user=user,
                            group=custom_group,
                            role=default_role,
                            is_primary=False
                        )
                except UserGroup.DoesNotExist:
                    # 如果没有对应的自定义用户组，跳过
                    pass

            except Group.DoesNotExist:
                # 如果组不存在，跳过
                continue

    @action(detail=False, methods=['get'], permission_classes=[permissions.IsAuthenticated])
    def current(self, request):
        """获取当前登录用户"""
        # 优化：预取相关数据避免N+1查询
        user = User.objects.select_related('profile').prefetch_related(
            'group_roles__group',
            'group_roles__role'
        ).get(pk=request.user.pk)

        serializer = self.get_serializer(user)
        return Response(serializer.data)

    @action(detail=True, methods=['post'], permission_classes=[permissions.IsAdminUser])
    def set_password(self, request, pk=None):
        """管理员设置用户密码"""
        user = self.get_object()
        password = request.data.get('password')

        if not password:
            return Response({"error": "密码不能为空"}, status=status.HTTP_400_BAD_REQUEST)

        user.set_password(password)
        user.save()
        return Response({"status": "密码已设置"})

    @action(detail=False, methods=['post'], permission_classes=[permissions.IsAuthenticated], url_path='change-password')
    def change_password(self, request):
        """用户修改自己的密码"""
        user = request.user
        old_password = request.data.get('old_password')
        new_password = request.data.get('new_password')

        if not old_password or not new_password:
            return Response({"error": "密码不能为空"}, status=status.HTTP_400_BAD_REQUEST)

        if not user.check_password(old_password):
            return Response({"error": "原密码不正确"}, status=status.HTTP_400_BAD_REQUEST)

        user.set_password(new_password)
        user.save()
        return Response({"status": "密码已修改"})

    @action(detail=True, methods=['patch'], permission_classes=[permissions.IsAuthenticated], url_path='admin-change-password')
    def admin_change_password(self, request, pk=None):
        """管理员修改其他用户密码"""
        # 检查权限：只有超级管理员可以修改其他用户密码
        if not request.user.is_superuser:
            return Response(
                {"error": "只有超级管理员可以修改其他用户密码"},
                status=status.HTTP_403_FORBIDDEN
            )

        user = self.get_object()
        password = request.data.get('password')

        if not password:
            return Response({"error": "密码不能为空"}, status=status.HTTP_400_BAD_REQUEST)

        user.set_password(password)
        user.save()
        return Response({"status": "密码已修改"})

    @action(detail=False, methods=['post'], url_path='avatar')
    def update_avatar(self, request):
        """更新用户头像"""
        user = request.user
        avatar_file = request.FILES.get('avatar')

        if not avatar_file:
            return Response({"error": "头像文件不能为空"}, status=status.HTTP_400_BAD_REQUEST)

        # 保存头像到用户的profile
        if hasattr(user, 'profile'):
            # 如果存在旧头像，先删除
            if user.profile.avatar:
                if os.path.isfile(user.profile.avatar.path):
                    os.remove(user.profile.avatar.path)

            # 保存新头像
            user.profile.avatar = avatar_file
            user.profile.save()

            # 返回头像URL
            serializer = UserSerializer(user, context={'request': request})
            return Response({
                "message": "头像已更新",
                "avatar_url": serializer.data.get('avatar_url')
            })

        return Response({"error": "用户资料不存在"}, status=status.HTTP_400_BAD_REQUEST)

    def get_serializer_class(self):
        """根据请求获取适当的序列化器"""
        if self.action == 'retrieve' and self.request.query_params.get('with_groups', 'false').lower() == 'true':
            return UserWithGroupsSerializer
        return UserSerializer

    @action(detail=True, methods=['get'])
    def groups(self, request, pk=None):
        """获取用户的组和角色信息"""
        user = self.get_object()
        user_groups = UserGroupRole.objects.filter(user=user).select_related('group', 'role')
        serializer = UserGroupRoleSerializer(user_groups, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def add_to_group(self, request, pk=None):
        """将用户添加到组并分配角色"""
        user = self.get_object()

        # 验证请求数据
        serializer = UserGroupRoleSerializer(data={
            'user': user.id,
            'group': request.data.get('group'),
            'role': request.data.get('role'),
            'is_primary': request.data.get('is_primary', False)
        })

        if serializer.is_valid():
            # 如果此用户已有主要组且新组也被标记为主要组，则将旧主要组更新为非主要
            if serializer.validated_data.get('is_primary'):
                UserGroupRole.objects.filter(user=user, is_primary=True).update(is_primary=False)

            # 保存新的用户组角色关联
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=True, methods=['delete'], url_path='remove-from-group/(?P<group_id>[^/.]+)')
    def remove_from_group(self, request, pk=None, group_id=None):
        """将用户从组中移除"""
        user = self.get_object()

        try:
            user_group = UserGroupRole.objects.get(user=user, group_id=group_id)
            user_group.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
        except UserGroupRole.DoesNotExist:
            return Response({"error": "用户不在指定组中"}, status=status.HTTP_404_NOT_FOUND)


class HostViewSet(viewsets.ModelViewSet):
    """主机视图集"""
    queryset = Host.objects.all()
    serializer_class = HostSerializer
    permission_classes = [permissions.IsAuthenticated]

    def create(self, request, *args, **kwargs):
        """创建主机并添加到Ansible Inventory"""
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        host = serializer.save()

        # 将主机添加到Ansible Inventory - 已迁移到ansible_management应用
        # try:
        #     # 获取主机组名，默认为'ungrouped'
        #     group_name = request.data.get('ansible_group', 'ungrouped')
        #     # 添加到inventory
        #     add_result = add_host_to_inventory(serializer.data, group_name)
        #     # 记录结果
        #     if add_result['status'] == 'success':
        #         logger.info(f"主机 {host.hostname} 已成功添加到Ansible Inventory")
        #     else:
        #         logger.warning(f"主机 {host.hostname} 添加到Ansible Inventory失败: {add_result.get('error', '未知错误')}")
        # except Exception as e:
        #     logger.error(f"添加主机到Ansible Inventory时出错: {str(e)}")

        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def update(self, request, *args, **kwargs):
        """更新主机并更新Ansible Inventory"""
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        host = serializer.save()

        # 更新Ansible Inventory中的主机 - 已迁移到ansible_management应用
        # try:
        #     # 获取主机组名，默认为'ungrouped'
        #     group_name = request.data.get('ansible_group', 'ungrouped')
        #     # 准备主机数据，包含密码等敏感信息
        #     host_data = serializer.data.copy()
        #     # 从数据库实例获取完整信息，因为序列化器可能不包含敏感字段
        #     host_data.update({
        #         'password': host.password if host.auth_type == 'password' else '',
        #         'private_key': host.private_key if host.auth_type == 'key' else '',
        #         'passphrase': host.passphrase or '',
        #         'auth_type': host.auth_type,
        #         'hostname': host.hostname,
        #         'public_ip': host.public_ip,
        #         'port': host.port,
        #         'username': host.username
        #     })
        #     # 更新inventory
        #     update_result = add_host_to_inventory(host_data, group_name)
        #     # 记录结果
        #     if update_result['status'] == 'success':
        #         logger.info(f"主机 {host.hostname} 在Ansible Inventory中已更新")
        #     else:
        #         logger.warning(f"主机 {host.hostname} 在Ansible Inventory中更新失败: {update_result.get('error', '未知错误')}")
        # except Exception as e:
        #     logger.error(f"更新Ansible Inventory中的主机时出错: {str(e)}")

        return Response(serializer.data)

    def destroy(self, request, *args, **kwargs):
        """删除主机并从Ansible Inventory中移除"""
        instance = self.get_object()
        hostname = instance.hostname
        public_ip = instance.public_ip

        # 从Ansible Inventory中移除主机 - 已迁移到ansible_management应用
        # try:
        #     # 尝试使用主机名和IP地址移除
        #     remove_result = remove_host_from_inventory(hostname)
        #     if remove_result['status'] != 'success' and public_ip:
        #         remove_result = remove_host_from_inventory(public_ip)
        #     # 记录结果
        #     if remove_result['status'] == 'success':
        #         logger.info(f"主机 {hostname} 已从Ansible Inventory中移除")
        #     else:
        #         logger.warning(f"主机 {hostname} 从Ansible Inventory中移除失败: {remove_result.get('error', '未知错误')}")
        # except Exception as e:
        #     logger.error(f"从Ansible Inventory中移除主机时出错: {str(e)}")

        # 删除数据库中的主机记录
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    @action(detail=True, methods=['post'])
    def check_connection(self, request, pk=None):
        """检查主机连接状态"""
        host = self.get_object()

        # 获取认证信息
        auth_type = host.auth_type
        username = host.username
        password = host.password if auth_type == 'password' else None
        private_key = host.private_key if auth_type == 'key' else None
        passphrase = host.passphrase if auth_type == 'key' and host.passphrase else None

        try:
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 设置连接超时
            connect_kwargs = {
                'hostname': host.public_ip,
                'port': host.port,
                'username': username,
                'timeout': 10
            }

            # 根据认证方式设置认证信息
            if auth_type == 'password':
                connect_kwargs['password'] = password
            else:
                if private_key:
                    # 创建私钥对象
                    private_key_file = io.StringIO(private_key)
                    if passphrase:
                        pkey = paramiko.RSAKey.from_private_key(private_key_file, password=passphrase)
                    else:
                        pkey = paramiko.RSAKey.from_private_key(private_key_file)
                    connect_kwargs['pkey'] = pkey

            # 尝试连接
            client.connect(**connect_kwargs)

            # 连接成功，更新主机状态
            host.status = 'online'
            host.save()

            # 关闭连接
            client.close()

            return Response({
                'status': 'success',
                'message': f'成功连接到主机 {host.hostname}'
            })
        except (paramiko.AuthenticationException, paramiko.SSHException) as e:
            # 认证失败或SSH错误
            host.status = 'offline'
            host.save()
            return Response({
                'status': 'error',
                'message': f'连接失败: {str(e)}'
            }, status=status.HTTP_400_BAD_REQUEST)
        except socket.timeout:
            # 连接超时
            host.status = 'offline'
            host.save()
            return Response({
                'status': 'error',
                'message': '连接超时'
            }, status=status.HTTP_400_BAD_REQUEST)
        except Exception as e:
            # 其他错误
            host.status = 'offline'
            host.save()
            return Response({
                'status': 'error',
                'message': f'连接失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['post'])
    def fetch_host_info(self, request):
        """获取主机信息"""
        # 获取请求参数
        public_ip = request.data.get('public_ip')
        port = request.data.get('port', 22)
        username = request.data.get('username')
        auth_type = request.data.get('auth_type', 'password')
        password = request.data.get('password')
        private_key = request.data.get('private_key')
        passphrase = request.data.get('passphrase')
        use_sudo = request.data.get('use_sudo', False)
        sudo_pass = request.data.get('sudo_pass')

        # 验证必要参数
        if not public_ip or not username:
            return Response({
                'status': 'error',
                'error': '缺少必要参数: public_ip, username'
            }, status=status.HTTP_400_BAD_REQUEST)

        # 根据认证方式验证凭据
        if auth_type == 'password' and not password:
            return Response({
                'status': 'error',
                'error': '使用密码认证时，必须提供密码'
            }, status=status.HTTP_400_BAD_REQUEST)
        elif auth_type == 'key' and not private_key:
            return Response({
                'status': 'error',
                'error': '使用密钥认证时，必须提供私钥'
            }, status=status.HTTP_400_BAD_REQUEST)

        # 功能已迁移到ansible_management应用
        return Response({
            'status': 'error',
            'message': '此功能已迁移到ansible_management应用'
        }, status=status.HTTP_501_NOT_IMPLEMENTED)

    @action(detail=True, methods=['get'])
    def metrics(self, request, pk=None):
        """获取主机实时指标"""
        host = self.get_object()

        # 获取认证信息
        auth_type = host.auth_type
        username = host.username
        password = host.password if auth_type == 'password' else None
        private_key = host.private_key if auth_type == 'key' else None
        passphrase = host.passphrase if auth_type == 'key' and host.passphrase else None

        # 功能已迁移到ansible_management应用
        return Response({
            'status': 'error',
            'message': '此功能已迁移到ansible_management应用'
        }, status=status.HTTP_501_NOT_IMPLEMENTED)

    @action(detail=True, methods=['get'])
    def files(self, request, pk=None):
        """获取主机文件内容"""
        host = self.get_object()
        file_path = request.query_params.get('path')

        if not file_path:
            return Response({
                'status': 'error',
                'error': '缺少必要参数: path'
            }, status=status.HTTP_400_BAD_REQUEST)

        # 获取认证信息
        auth_type = host.auth_type
        username = host.username
        password = host.password if auth_type == 'password' else None
        private_key = host.private_key if auth_type == 'key' else None
        passphrase = host.passphrase if auth_type == 'key' and host.passphrase else None

        # 功能已迁移到ansible_management应用
        return Response({
            'status': 'error',
            'message': '此功能已迁移到ansible_management应用'
        }, status=status.HTTP_501_NOT_IMPLEMENTED)

    @action(detail=True, methods=['post'])
    def add_to_ansible_inventory(self, request, pk=None):
        """将主机添加到Ansible Inventory"""
        host = self.get_object()
        group_name = request.data.get('group', 'ungrouped')

        # 功能已迁移到ansible_management应用
        return Response({
            'status': 'error',
            'message': '此功能已迁移到ansible_management应用'
        }, status=status.HTTP_501_NOT_IMPLEMENTED)

    @action(detail=True, methods=['delete'])
    def remove_from_ansible_inventory(self, request, pk=None):
        """从Ansible Inventory中移除主机"""
        host = self.get_object()

        # 功能已迁移到ansible_management应用
        return Response({
            'status': 'error',
            'message': '此功能已迁移到ansible_management应用'
        }, status=status.HTTP_501_NOT_IMPLEMENTED)

    @action(detail=True, methods=['post'], url_path='test')
    def test_connection(self, request, pk=None):
        """测试主机连接（使用异步任务）"""
        host = self.get_object()
        result = check_single_host(host.id)

        if result['success']:
            return Response({
                'success': True,
                'online': result['online'],
                'status': result['status'],
                'message': result['message']
            })
        else:
            return Response({
                'success': False,
                'message': result['message']
            }, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=True, methods=['post'], url_path='detect-info')
    def detect_system_info(self, request, pk=None):
        """自动检测主机系统信息"""
        host = self.get_object()
        result = auto_detect_host_info(host.id)

        if result['success']:
            # 重新获取更新后的主机信息
            updated_host = Host.objects.get(id=host.id)
            serializer = self.get_serializer(updated_host)

            return Response({
                'success': True,
                'message': result['message'],
                'data': serializer.data,
                'detected_info': result.get('data', {})
            })
        else:
            return Response({
                'success': False,
                'message': result['message']
            }, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=True, methods=['post'], url_path='refresh')
    def refresh_info(self, request, pk=None):
        """刷新单个主机信息"""
        try:
            host = self.get_object()
            from .tasks import auto_detect_enhanced_host_info

            result = auto_detect_enhanced_host_info(host.id)

            if result['success']:
                # 重新获取更新后的主机信息
                updated_host = Host.objects.get(id=host.id)
                serializer = self.get_serializer(updated_host)

                return Response({
                    'success': True,
                    'message': result['message'],
                    'data': serializer.data,
                    'updated_fields': result.get('updated_fields', [])
                })
            else:
                return Response({
                    'success': False,
                    'message': result['message']
                }, status=status.HTTP_400_BAD_REQUEST)

        except Exception as e:
            logger.error(f"刷新主机信息失败: {e}")
            return Response({
                'success': False,
                'message': f'刷新失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['post'], url_path='refresh-all')
    def refresh_all(self, request):
        """刷新所有主机信息"""
        try:
            from .tasks import refresh_all_hosts

            result = refresh_all_hosts()

            if result['success']:
                return Response({
                    'success': True,
                    'message': result['message'],
                    'total': result.get('total', 0),
                    'success_count': result.get('success_count', 0),
                    'fail_count': result.get('fail_count', 0)
                })
            else:
                return Response({
                    'success': False,
                    'message': result['message']
                }, status=status.HTTP_400_BAD_REQUEST)

        except Exception as e:
            logger.error(f"刷新所有主机信息失败: {e}")
            return Response({
                'success': False,
                'message': f'刷新失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['post'], url_path='batch-refresh')
    def batch_refresh(self, request):
        """批量刷新主机信息"""
        try:
            host_ids = request.data.get('host_ids', [])
            if not host_ids:
                return Response({
                    'success': False,
                    'message': '请选择要刷新的主机'
                }, status=status.HTTP_400_BAD_REQUEST)

            from .tasks import batch_auto_detect_hosts

            result = batch_auto_detect_hosts(host_ids)

            if result['success']:
                # 统计成功和失败的数量
                success_count = sum(1 for r in result['results'] if r['result']['success'])
                fail_count = len(host_ids) - success_count

                return Response({
                    'success': True,
                    'message': f'批量刷新完成：成功 {success_count} 台，失败 {fail_count} 台',
                    'total': len(host_ids),
                    'success_count': success_count,
                    'fail_count': fail_count,
                    'results': result['results']
                })
            else:
                return Response({
                    'success': False,
                    'message': result['message']
                }, status=status.HTTP_400_BAD_REQUEST)

        except Exception as e:
            logger.error(f"批量刷新主机信息失败: {e}")
            return Response({
                'success': False,
                'message': f'批量刷新失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=True, methods=['post'], url_path='upload-file')
    def upload_file(self, request, pk=None):
        """上传文件到主机"""
        host = self.get_object()
        uploaded_file = request.FILES.get('file')
        target_path = request.data.get('target_path', 'CURRENT_DIR')

        if not uploaded_file:
            return Response({
                'status': 'error',
                'error': '没有上传文件'
            }, status=status.HTTP_400_BAD_REQUEST)

        try:
            # 建立SSH连接
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 连接参数
            connect_kwargs = {
                'hostname': host.public_ip,
                'port': host.port,
                'username': host.username,
                'timeout': 10
            }

            # 根据认证类型添加认证信息
            if host.auth_type == 'password':
                connect_kwargs['password'] = host.password
            else:  # key认证
                private_key = paramiko.RSAKey.from_private_key(
                    io.StringIO(host.private_key),
                    password=host.passphrase if host.passphrase else None
                )
                connect_kwargs['pkey'] = private_key

            client.connect(**connect_kwargs)

            # 创建SFTP客户端
            sftp = client.open_sftp()

            # 如果前端传递的是具体路径，直接使用
            if target_path and target_path != 'CURRENT_DIR' and target_path.startswith('/'):
                # 前端已经通过SSH会话获取了真实路径，直接使用
                logger.info(f"使用前端传递的当前目录: {target_path}")
            elif target_path == 'CURRENT_DIR':
                # 兜底：如果还是CURRENT_DIR标记，使用家目录
                target_path = '/root/' if host.username == 'root' else f'/home/{host.username}/'
                logger.warning(f"使用默认家目录: {target_path}")

            # 如果是相对路径或~路径，需要展开
            elif target_path.startswith('~/'):
                if host.username == 'root':
                    target_path = target_path.replace('~/', '/root/')
                else:
                    target_path = target_path.replace('~/', f'/home/{host.username}/')
            elif target_path == '~/' or target_path == '~':
                if host.username == 'root':
                    target_path = '/root/'
                else:
                    target_path = f'/home/{host.username}/'

            # 确保路径以 / 结尾
            if not target_path.endswith('/'):
                target_path += '/'

            # 上传文件
            remote_file_path = os.path.join(target_path, uploaded_file.name).replace('\\', '/')

            # 将上传的文件内容写入临时文件
            with tempfile.NamedTemporaryFile(delete=False) as temp_file:
                for chunk in uploaded_file.chunks():
                    temp_file.write(chunk)
                temp_file_path = temp_file.name

            # 上传文件
            try:
                sftp.put(temp_file_path, remote_file_path)
                logger.info(f"文件上传成功: {remote_file_path}")
            except Exception as e:
                logger.error(f"SFTP上传失败: {e}")
                # 清理临时文件
                os.unlink(temp_file_path)
                raise e

            # 清理临时文件
            os.unlink(temp_file_path)

            # 关闭连接
            sftp.close()
            client.close()

            return Response({
                'status': 'success',
                'message': f'文件 {uploaded_file.name} 上传成功',
                'file_path': remote_file_path,
                'file_size': uploaded_file.size,
                'actual_path': target_path  # 返回实际上传的目录路径
            })

        except Exception as e:
            logger.error(f"文件上传失败: {e}")
            return Response({
                'status': 'error',
                'error': str(e)
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=True, methods=['get'], url_path='list-files')
    def list_files(self, request, pk=None):
        """列出主机上的文件"""
        host = self.get_object()
        path = request.query_params.get('path', '~')

        try:
            # 建立SSH连接
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 连接参数
            connect_kwargs = {
                'hostname': host.public_ip,
                'port': host.port,
                'username': host.username,
                'timeout': 10
            }

            # 根据认证类型添加认证信息
            if host.auth_type == 'password':
                connect_kwargs['password'] = host.password
            else:  # key认证
                private_key = paramiko.RSAKey.from_private_key(
                    io.StringIO(host.private_key),
                    password=host.passphrase if host.passphrase else None
                )
                connect_kwargs['pkey'] = private_key

            client.connect(**connect_kwargs)

            # 创建SFTP客户端
            sftp = client.open_sftp()

            # 展开路径中的 ~ 符号
            if path.startswith('~/'):
                if host.username == 'root':
                    path = path.replace('~/', '/root/')
                else:
                    path = path.replace('~/', f'/home/{host.username}/')
            elif path == '~':
                if host.username == 'root':
                    path = '/root/'
                else:
                    path = f'/home/{host.username}/'

            # 列出文件
            files = []
            try:
                file_list = sftp.listdir_attr(path)
                for file_attr in file_list:
                    file_info = {
                        'name': file_attr.filename,
                        'path': os.path.join(path, file_attr.filename),
                        'size': file_attr.st_size,
                        'modified': file_attr.st_mtime,
                        'permissions': oct(file_attr.st_mode)[-3:],
                        'type': 'directory' if stat.S_ISDIR(file_attr.st_mode) else 'file'
                    }
                    files.append(file_info)
            except Exception as e:
                logger.error(f"列出文件失败: {e}")
                return Response({
                    'status': 'error',
                    'error': f'无法访问路径: {path}'
                }, status=status.HTTP_400_BAD_REQUEST)

            # 关闭连接
            sftp.close()
            client.close()

            return Response({
                'status': 'success',
                'path': path,
                'files': sorted(files, key=lambda x: (x['type'] == 'file', x['name']))
            })

        except Exception as e:
            logger.error(f"列出文件失败: {e}")
            return Response({
                'status': 'error',
                'error': str(e)
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=True, methods=['get'], url_path='download-file')
    def download_file(self, request, pk=None):
        """下载单个文件"""
        host = self.get_object()
        file_path = request.query_params.get('file_path')

        if not file_path:
            return Response({
                'status': 'error',
                'error': '文件路径不能为空'
            }, status=status.HTTP_400_BAD_REQUEST)

        try:
            # 建立SSH连接
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 连接参数
            connect_kwargs = {
                'hostname': host.public_ip,
                'port': host.port,
                'username': host.username,
                'timeout': 10
            }

            # 根据认证类型添加认证信息
            if host.auth_type == 'password':
                connect_kwargs['password'] = host.password
            else:  # key认证
                private_key = paramiko.RSAKey.from_private_key(
                    io.StringIO(host.private_key),
                    password=host.passphrase if host.passphrase else None
                )
                connect_kwargs['pkey'] = private_key

            client.connect(**connect_kwargs)

            # 创建SFTP客户端
            sftp = client.open_sftp()

            # 创建临时文件
            temp_file = tempfile.NamedTemporaryFile(delete=False)
            temp_file_path = temp_file.name
            temp_file.close()

            # 下载文件
            sftp.get(file_path, temp_file_path)

            # 关闭连接
            sftp.close()
            client.close()

            # 获取文件名
            filename = os.path.basename(file_path)

            # 返回文件响应 - 使用最简单的方法
            response = FileResponse(
                open(temp_file_path, 'rb'),
                as_attachment=True,
                filename=filename
            )

            # 强制设置为二进制流，避免浏览器添加.txt
            response['Content-Type'] = 'application/octet-stream'
            response['Content-Disposition'] = f'attachment; filename="{filename}"'

            # 设置清理临时文件的回调
            def cleanup():
                try:
                    os.unlink(temp_file_path)
                except:
                    pass

            response.close = cleanup
            return response

        except Exception as e:
            logger.error(f"文件下载失败: {e}")
            return Response({
                'status': 'error',
                'error': str(e)
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=True, methods=['post'], url_path='download-files')
    def download_files(self, request, pk=None):
        """批量下载文件（打包为ZIP）"""
        host = self.get_object()
        file_paths = request.data.get('file_paths', [])

        if not file_paths:
            return Response({
                'status': 'error',
                'error': '文件路径列表不能为空'
            }, status=status.HTTP_400_BAD_REQUEST)

        try:
            # 建立SSH连接
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 连接参数
            connect_kwargs = {
                'hostname': host.public_ip,
                'port': host.port,
                'username': host.username,
                'timeout': 10
            }

            # 根据认证类型添加认证信息
            if host.auth_type == 'password':
                connect_kwargs['password'] = host.password
            else:  # key认证
                private_key = paramiko.RSAKey.from_private_key(
                    io.StringIO(host.private_key),
                    password=host.passphrase if host.passphrase else None
                )
                connect_kwargs['pkey'] = private_key

            client.connect(**connect_kwargs)

            # 创建SFTP客户端
            sftp = client.open_sftp()

            # 创建临时ZIP文件
            temp_zip = tempfile.NamedTemporaryFile(delete=False, suffix='.zip')
            temp_zip_path = temp_zip.name
            temp_zip.close()

            with zipfile.ZipFile(temp_zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_file:
                for file_path in file_paths:
                    try:
                        # 创建临时文件存储下载的文件
                        temp_file = tempfile.NamedTemporaryFile(delete=False)
                        temp_file_path = temp_file.name
                        temp_file.close()

                        # 下载文件
                        sftp.get(file_path, temp_file_path)

                        # 添加到ZIP
                        filename = os.path.basename(file_path)
                        zip_file.write(temp_file_path, filename)

                        # 清理临时文件
                        os.unlink(temp_file_path)

                    except Exception as e:
                        logger.warning(f"跳过文件 {file_path}: {e}")
                        continue

            # 关闭连接
            sftp.close()
            client.close()

            # 返回ZIP文件响应
            response = FileResponse(
                open(temp_zip_path, 'rb'),
                as_attachment=True,
                filename=f'files_{datetime.now().strftime("%Y%m%d_%H%M%S")}.zip'
            )

            # 设置清理临时文件的回调
            def cleanup():
                try:
                    os.unlink(temp_zip_path)
                except:
                    pass

            response.close = cleanup
            return response

        except Exception as e:
            logger.error(f"批量下载失败: {e}")
            return Response({
                'status': 'error',
                'error': str(e)
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=True, methods=['get'], url_path='get-pwd')
    def get_pwd(self, request, pk=None):
        """获取当前工作目录"""
        host = self.get_object()

        try:
            # 建立SSH连接
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 连接参数
            connect_kwargs = {
                'hostname': host.public_ip,
                'port': host.port,
                'username': host.username,
                'timeout': 10
            }

            # 根据认证类型添加认证信息
            if host.auth_type == 'password':
                connect_kwargs['password'] = host.password
            else:  # key认证
                private_key = paramiko.RSAKey.from_private_key(
                    io.StringIO(host.private_key),
                    password=host.passphrase if host.passphrase else None
                )
                connect_kwargs['pkey'] = private_key

            client.connect(**connect_kwargs)

            # 执行pwd命令获取当前工作目录
            stdin, stdout, stderr = client.exec_command('pwd')
            pwd = stdout.read().decode().strip()

            # 关闭连接
            client.close()

            return Response({
                'status': 'success',
                'pwd': pwd
            })

        except Exception as e:
            logger.error(f"获取当前工作目录失败: {e}")
            return Response({
                'status': 'error',
                'error': str(e)
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class HostTypeViewSet(viewsets.ModelViewSet):
    """主机类型视图集"""
    queryset = HostType.objects.all()
    serializer_class = HostTypeSerializer
    permission_classes = [permissions.IsAuthenticated]


class HostGroupViewSet(viewsets.ModelViewSet):
    """主机组视图集"""
    queryset = HostGroup.objects.all()
    serializer_class = HostGroupSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        """根据查询参数过滤主机组"""
        queryset = HostGroup.objects.all()

        # 过滤参数
        environment = self.request.query_params.get('environment', None)
        is_active = self.request.query_params.get('is_active', None)
        owner_id = self.request.query_params.get('owner', None)
        search = self.request.query_params.get('search', None)

        if environment:
            queryset = queryset.filter(environment=environment)
        if is_active is not None:
            queryset = queryset.filter(is_active=is_active.lower() == 'true')
        if owner_id:
            queryset = queryset.filter(owner_id=owner_id)
        if search:
            queryset = queryset.filter(
                Q(name__icontains=search) |
                Q(description__icontains=search) |
                Q(tags__icontains=search)
            )

        return queryset.order_by('-created_at')

    @action(detail=True, methods=['post'])
    def add_hosts(self, request, pk=None):
        """向主机组添加主机"""
        host_group = self.get_object()
        host_ids = request.data.get('host_ids', [])

        if not host_ids:
            return Response({
                'success': False,
                'message': '请选择要添加的主机'
            }, status=status.HTTP_400_BAD_REQUEST)

        try:
            hosts = Host.objects.filter(id__in=host_ids)
            host_group.hosts.add(*hosts)

            return Response({
                'success': True,
                'message': f'成功添加 {len(hosts)} 台主机到主机组 {host_group.name}',
                'added_count': len(hosts)
            })
        except Exception as e:
            return Response({
                'success': False,
                'message': f'添加主机失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=True, methods=['post'])
    def remove_hosts(self, request, pk=None):
        """从主机组移除主机"""
        host_group = self.get_object()
        host_ids = request.data.get('host_ids', [])

        if not host_ids:
            return Response({
                'success': False,
                'message': '请选择要移除的主机'
            }, status=status.HTTP_400_BAD_REQUEST)

        try:
            hosts = Host.objects.filter(id__in=host_ids)
            host_group.hosts.remove(*hosts)

            return Response({
                'success': True,
                'message': f'成功从主机组 {host_group.name} 移除 {len(hosts)} 台主机',
                'removed_count': len(hosts)
            })
        except Exception as e:
            return Response({
                'success': False,
                'message': f'移除主机失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=True, methods=['get'])
    def hosts(self, request, pk=None):
        """获取主机组下的所有主机"""
        host_group = self.get_object()
        hosts = host_group.hosts.all()

        # 可以添加分页和过滤
        status_filter = request.query_params.get('status', None)
        if status_filter:
            hosts = hosts.filter(status=status_filter)

        serializer = HostSerializer(hosts, many=True)
        return Response({
            'host_group': host_group.name,
            'total_count': hosts.count(),
            'hosts': serializer.data
        })

    @action(detail=True, methods=['get'])
    def statistics(self, request, pk=None):
        """获取主机组统计信息"""
        host_group = self.get_object()
        hosts = host_group.hosts.all()

        stats = {
            'total_hosts': hosts.count(),
            'online_hosts': hosts.filter(status='online').count(),
            'offline_hosts': hosts.filter(status='offline').count(),
            'host_types': {},
            'environments': {}
        }

        # 统计主机类型
        for host_type in hosts.values_list('type', flat=True).distinct():
            if host_type:
                stats['host_types'][host_type] = hosts.filter(type=host_type).count()

        # 统计系统类型
        for system_type in hosts.values_list('system_type', flat=True).distinct():
            if system_type:
                stats['environments'][system_type] = hosts.filter(system_type=system_type).count()

        return Response(stats)


class ConnectionRecordViewSet(viewsets.ModelViewSet):
    """连接记录视图集"""
    queryset = ConnectionRecord.objects.all()
    serializer_class = ConnectionRecordSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        queryset = ConnectionRecord.objects.all()
        host_id = self.request.query_params.get('host', None)
        user_id = self.request.query_params.get('user', None)
        is_active = self.request.query_params.get('is_active', None)

        if host_id is not None:
            queryset = queryset.filter(host_id=host_id)
        if user_id is not None:
            queryset = queryset.filter(user_id=user_id)
        if is_active is not None:
            queryset = queryset.filter(is_active=is_active.lower() == 'true')

        return queryset.order_by('-connected_at')

    def get_queryset(self):
        queryset = super().get_queryset()
        # 管理员可以查看所有记录，普通用户只能查看自己的记录
        if not self.request.user.is_staff:
            queryset = queryset.filter(user=self.request.user)
        return queryset

    @action(detail=True, methods=['get'])
    def replay(self, request, pk=None):
        """获取连接的命令回放数据"""
        connection = self.get_object()
        logs = CommandLog.objects.filter(connection=connection).order_by('executed_at')

        # 准备回放数据
        replay_data = []
        for log in logs:
            entry = {
                'command_id': log.id,
                'command': log.command,
                'executed_at': log.executed_at.isoformat(),
                'duration': log.duration or 0,
                'exit_code': log.exit_code or 0,
            }

            # 获取带时间戳的输出事件
            output_events = log.get_output_events()
            if output_events:
                entry['output_events'] = output_events
            else:
                # 否则使用普通输出，但没有精确的时间信息
                if log.output:
                    lines = log.output.split('\n')
                    output_events = []
                    base_time = log.executed_at
                    for i, line in enumerate(lines):
                        if line:  # 跳过空行
                            # 模拟每行输出的时间间隔（0.1秒）
                            timestamp = (base_time + timedelta(seconds=0.1 * i)).isoformat()
                            output_events.append({
                                'content': line,
                                'timestamp': timestamp
                            })
                    entry['output_events'] = output_events
                else:
                    entry['output_events'] = []

            replay_data.append(entry)

        return Response({
            'connection_id': connection.id,
            'user': connection.user.username,
            'host': {
                'hostname': connection.host.hostname,
                'ip': connection.host.public_ip or connection.host.ip
            },
            'connected_at': connection.connected_at.isoformat(),
            'disconnected_at': connection.disconnected_at.isoformat() if connection.disconnected_at else None,
            'commands': replay_data
        })

    @action(detail=True, methods=['post'])
    def disconnect(self, request, pk=None):
        """断开连接"""
        connection = self.get_object()
        if connection.is_active:
            connection.is_active = False
            connection.disconnected_at = timezone.now()
            connection.save()
            return Response({"status": "success", "message": "已断开连接"})
        return Response(
            {"status": "error", "message": "连接已断开"},
            status=status.HTTP_400_BAD_REQUEST
        )

    @action(detail=True, methods=['post'])
    def execute_command(self, request, pk=None):
        """执行命令"""
        connection = self.get_object()
        command = request.data.get('command')
        if not command:
            return Response(
                {"status": "error", "message": "命令不能为空"},
                status=status.HTTP_400_BAD_REQUEST
            )

        if not connection.is_active:
            return Response(
                {"status": "error", "message": "连接已断开"},
                status=status.HTTP_400_BAD_REQUEST
            )

        try:
            host = connection.host
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 记录开始时间
            start_time = timezone.now()

            # 根据认证类型选择不同的连接方式
            if host.auth_type == 'password':
                client.connect(
                    hostname=host.public_ip or host.ip,
                    port=host.port,
                    username=host.username,
                    password=host.password,
                    timeout=5
                )
            else:  # 密钥认证
                private_key = paramiko.RSAKey.from_private_key(
                    io.StringIO(host.private_key),
                    password=host.passphrase
                ) if host.passphrase else paramiko.RSAKey.from_private_key(io.StringIO(host.private_key))

                client.connect(
                    hostname=host.public_ip or host.ip,
                    port=host.port,
                    username=host.username,
                    pkey=private_key,
                    timeout=5
                )

            # 创建一个新的会话
            channel = client.get_transport().open_session()
            channel.settimeout(10)
            channel.set_combine_stderr(True)  # 合并标准错误到标准输出

            # 执行命令
            channel.exec_command(command)

            # 实时读取输出并记录时间戳
            output_events = []
            output_buffer = ""

            while True:
                if channel.exit_status_ready():
                    # 如果还有剩余数据，读取它
                    if channel.recv_ready():
                        data = channel.recv(1024).decode('utf-8', errors='replace')
                        timestamp = timezone.now().isoformat()
                        output_events.append({
                            'content': data,
                            'timestamp': timestamp
                        })
                        output_buffer += data
                    break

                if channel.recv_ready():
                    data = channel.recv(1024).decode('utf-8', errors='replace')
                    timestamp = timezone.now().isoformat()
                    output_events.append({
                        'content': data,
                        'timestamp': timestamp
                    })
                    output_buffer += data

                # 避免CPU过度使用
                time.sleep(0.1)

            # 获取退出状态
            exit_code = channel.recv_exit_status()

            # 计算执行时长
            end_time = timezone.now()
            duration = (end_time - start_time).total_seconds()

            client.close()

            # 记录命令到原有系统
            command_log = CommandLog.objects.create(
                connection=connection,
                command=command,
                output=output_buffer,
                duration=duration,
                exit_code=exit_code,
                executed_at=start_time
            )

            # 设置输出事件
            command_log.set_output_events(output_events)
            command_log.save()

            # 记录命令到审计系统
            from .jumpserver_audit import jumpserver_audit

            # 查找对应的SSH会话
            try:
                from .models import SSHSession
                ssh_session = SSHSession.objects.filter(
                    user=request.user,
                    host=connection.host,
                    status='active'
                ).latest('start_time')

                # 记录命令到审计系统
                audit_service.log_command(
                    session_id=ssh_session.session_id,
                    command_type='input',
                    command=command,
                    output=output_buffer,
                    duration=duration,
                    exit_code=exit_code
                )

                # 记录回放数据
                audit_service.log_replay_data(
                    session_id=ssh_session.session_id,
                    timestamp=0,
                    data_type='input',
                    data=command + '\n'
                )

                for i, event in enumerate(output_events):
                    audit_service.log_replay_data(
                        session_id=ssh_session.session_id,
                        timestamp=i * 0.1,
                        data_type='output',
                        data=event['content']
                    )

            except Exception as e:
                logger.warning(f"记录审计数据失败: {str(e)}")

            # 更新命令数
            connection.command_count += 1
            connection.save()

            return Response({
                "status": "success",
                "command_id": command_log.id,
                "output": output_buffer,
                "exit_code": exit_code,
                "duration": duration
            })
        except Exception as e:
            return Response(
                {"status": "error", "message": f"执行失败: {str(e)}"},
                status=status.HTTP_400_BAD_REQUEST
            )


class CommandLogViewSet(viewsets.ReadOnlyModelViewSet):
    """命令日志视图集"""
    queryset = CommandLog.objects.all()
    serializer_class = CommandLogSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        queryset = CommandLog.objects.select_related('connection__user', 'connection__host')

        # 过滤参数
        host_id = self.request.query_params.get('host', None)
        user_id = self.request.query_params.get('user', None)
        connection_id = self.request.query_params.get('connection', None)
        risk_level = self.request.query_params.get('risk_level', None)
        is_dangerous = self.request.query_params.get('is_dangerous', None)
        start_date = self.request.query_params.get('start_date', None)
        end_date = self.request.query_params.get('end_date', None)

        if host_id is not None:
            queryset = queryset.filter(connection__host_id=host_id)
        if user_id is not None:
            queryset = queryset.filter(connection__user_id=user_id)
        if connection_id is not None:
            queryset = queryset.filter(connection_id=connection_id)
        if risk_level is not None:
            queryset = queryset.filter(risk_level=risk_level)
        if is_dangerous is not None:
            queryset = queryset.filter(is_dangerous=is_dangerous.lower() == 'true')
        if start_date is not None:
            queryset = queryset.filter(executed_at__gte=start_date)
        if end_date is not None:
            queryset = queryset.filter(executed_at__lte=end_date)

        return queryset.order_by('-executed_at')

    def get_queryset(self):
        queryset = super().get_queryset()
        # 管理员可以查看所有日志，普通用户只能查看自己的日志
        if not self.request.user.is_staff:
            queryset = queryset.filter(connection__user=self.request.user)
        return queryset


class AlertViewSet(viewsets.ModelViewSet):
    """告警视图集"""
    queryset = Alert.objects.all()
    serializer_class = AlertSerializer
    permission_classes = [permissions.IsAuthenticated]

    @action(detail=True, methods=['post'])
    def mark_as_read(self, request, pk=None):
        """标记为已读"""
        alert = self.get_object()
        alert.is_read = True
        alert.save()
        return Response({"status": "success", "message": "已标记为已读"})


class SystemMetricsView(APIView):
    """
    API endpoint for retrieving current system metrics
    """
    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            # 获取主机统计信息
            total_hosts = Host.objects.count()
            online_hosts = Host.objects.filter(status='online').count()

            # Get CPU usage
            cpu_usage = psutil.cpu_percent(interval=1)
            cpu_count = psutil.cpu_count()

            # Get memory usage
            memory = psutil.virtual_memory()
            memory_usage = memory.percent
            memory_total = memory.total
            memory_available = memory.available
            memory_used = memory.used

            # Get disk usage for the root partition
            disk = psutil.disk_usage('/')
            disk_usage = disk.percent
            disk_total = disk.total
            disk_free = disk.free
            disk_used = disk.used

            # Get network information
            net_io = psutil.net_io_counters()

            # 获取本月连接数量
            current_month_start = timezone.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0)
            monthly_connections = ConnectionRecord.objects.filter(
                connected_at__gte=current_month_start
            ).count()

            # 获取未读告警数量
            alert_count = Alert.objects.filter(is_read=False).count()

            data = {
                # 主机统计数据
                'total_hosts': total_hosts,
                'online_hosts': online_hosts,
                'monthly_connections': monthly_connections,
                'alert_count': alert_count,

                # 系统指标数据
                'cpu_usage': cpu_usage,
                'cpu_count': cpu_count,
                'memory_usage': memory_usage,
                'memory_total': memory_total,
                'memory_available': memory_available,
                'memory_used': memory_used,
                'disk_usage': disk_usage,
                'disk_total': disk_total,
                'disk_free': disk_free,
                'disk_used': disk_used,
                'network': {
                    'bytes_sent': net_io.bytes_sent,
                    'bytes_recv': net_io.bytes_recv,
                },
                'timestamp': datetime.now().isoformat()
            }

            return Response(data)
        except Exception as e:
            return Response(
                {'error': str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


class WebSSHView(APIView):
    """处理SSH连接的视图"""
    permission_classes = [permissions.IsAuthenticated]

    @method_decorator(csrf_exempt)
    def post(self, request, host_id):
        """创建SSH连接"""
        try:
            # 获取主机信息
            host = Host.objects.get(id=host_id)

            # 验证用户权限
            if not request.user.is_staff and not request.user.has_perm('cmdb.view_host'):
                return JsonResponse({
                    'status': 'error',
                    'message': '无权访问此主机'
                }, status=403)

            # 检查主机状态
            if host.status != 'online':
                return JsonResponse({
                    'status': 'error',
                    'message': '主机当前离线，无法连接'
                }, status=400)

            # 创建连接记录
            connection = ConnectionRecord.objects.create(
                user=request.user,
                host=host,
                is_active=True
            )

            # 创建SSH审计会话
            from .jumpserver_audit import jumpserver_audit
            client_ip = request.META.get('REMOTE_ADDR', '127.0.0.1')
            user_agent = request.META.get('HTTP_USER_AGENT', '')
            ssh_session = jumpserver_audit.create_session(
                user=request.user,
                host=host,
                client_ip=client_ip,
                user_agent=user_agent
            )

            try:
                # 创建SSH客户端
                client = paramiko.SSHClient()
                client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

                # 根据认证类型选择不同的连接方式
                if host.auth_type == 'password':
                    client.connect(
                        hostname=host.public_ip,
                        port=host.port,
                        username=host.username,
                        password=host.password,
                        timeout=10
                    )
                else:  # 密钥认证
                    if host.passphrase:
                        private_key = paramiko.RSAKey.from_private_key(
                            io.StringIO(host.private_key),
                            password=host.passphrase
                        )
                    else:
                        private_key = paramiko.RSAKey.from_private_key(
                            io.StringIO(host.private_key)
                        )

                    client.connect(
                        hostname=host.public_ip,
                        port=host.port,
                        username=host.username,
                        pkey=private_key,
                        timeout=10
                    )

                # 创建SSH会话
                session = client.get_transport().open_session()
                session.get_pty(term='xterm')
                session.invoke_shell()

                # 更新主机状态
                host.status = 'online'
                host.save()

                return JsonResponse({
                    'status': 'success',
                    'connection_id': connection.id,
                    'session_id': ssh_session.session_id,
                    'message': f'成功连接到 {host.hostname}'
                })

            except Exception as e:
                logger.error(f"SSH连接错误: {str(e)}")
                connection.is_active = False
                connection.disconnected_at = timezone.now()
                connection.save()

                # 如果连接失败，将主机状态设为离线
                host.status = 'offline'
                host.save()

                return JsonResponse({
                    'status': 'error',
                    'message': f'连接失败: {str(e)}'
                }, status=400)

        except Host.DoesNotExist:
            return JsonResponse({
                'status': 'error',
                'message': '主机不存在'
            }, status=404)
        except Exception as e:
            logger.error(f"WebSSH处理错误: {str(e)}")
            return JsonResponse({
                'status': 'error',
                'message': f'处理请求时出错: {str(e)}'
            }, status=500)

    @method_decorator(csrf_exempt)
    def put(self, request, host_id):
        """执行SSH命令"""
        try:
            # 获取连接ID和命令
            data = json.loads(request.body)
            connection_id = data.get('connection_id')
            command = data.get('command')

            if not connection_id or not command:
                return JsonResponse({
                    'status': 'error',
                    'message': '缺少必要参数'
                }, status=400)

            # 获取连接记录
            try:
                connection = ConnectionRecord.objects.get(id=connection_id)

                # 验证用户权限
                if connection.user != request.user and not request.user.is_staff:
                    return JsonResponse({
                        'status': 'error',
                        'message': '无权访问此连接'
                    }, status=403)

                # 检查连接状态
                if not connection.is_active:
                    return JsonResponse({
                        'status': 'error',
                        'message': '连接已断开'
                    }, status=400)

                # 获取主机
                host = connection.host

                # 执行命令
                client = paramiko.SSHClient()
                client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

                # 根据认证类型选择不同的连接方式
                if host.auth_type == 'password':
                    client.connect(
                        hostname=host.public_ip,
                        port=host.port,
                        username=host.username,
                        password=host.password,
                        timeout=10
                    )
                else:  # 密钥认证
                    if host.passphrase:
                        private_key = paramiko.RSAKey.from_private_key(
                            io.StringIO(host.private_key),
                            password=host.passphrase
                        )
                    else:
                        private_key = paramiko.RSAKey.from_private_key(
                            io.StringIO(host.private_key)
                        )

                    client.connect(
                        hostname=host.public_ip,
                        port=host.port,
                        username=host.username,
                        pkey=private_key,
                        timeout=10
                    )

                # 记录开始时间
                import time
                start_time = time.time()

                stdin, stdout, stderr = client.exec_command(command, timeout=30)
                output = stdout.read().decode('utf-8', errors='ignore')
                error = stderr.read().decode('utf-8', errors='ignore')
                exit_code = stdout.channel.recv_exit_status()

                # 计算执行时间
                execution_time = time.time() - start_time

                client.close()

                # 合并输出
                full_output = output
                if error:
                    full_output += f"\n{error}"

                # 创建输出事件（用于回放）
                output_events = []
                if output:
                    output_events.append({
                        'type': 'stdout',
                        'content': output,
                        'timestamp': time.time()
                    })
                if error:
                    output_events.append({
                        'type': 'stderr',
                        'content': error,
                        'timestamp': time.time()
                    })

                # 记录命令
                command_log = CommandLog.objects.create(
                    connection=connection,
                    command=command,
                    output=full_output,
                    exit_code=exit_code,
                    duration=execution_time
                )

                # 设置输出事件
                command_log.set_output_events(output_events)
                command_log.save()

                # 更新命令数
                connection.command_count += 1
                connection.last_activity = timezone.now()
                connection.save()

                return JsonResponse({
                    'status': 'success',
                    'command_id': command_log.id,
                    'output': full_output,
                    'exit_code': exit_code,
                    'duration': execution_time,
                    'risk_level': command_log.risk_level,
                    'is_dangerous': command_log.is_dangerous
                })

            except ConnectionRecord.DoesNotExist:
                return JsonResponse({
                    'status': 'error',
                    'message': '连接不存在'
                }, status=404)

        except Exception as e:
            logger.error(f"执行SSH命令错误: {str(e)}")
            return JsonResponse({
                'status': 'error',
                'message': f'执行命令时出错: {str(e)}'
            }, status=500)

    @method_decorator(csrf_exempt)
    def delete(self, request, host_id):
        """断开SSH连接"""
        try:
            # 获取连接ID
            connection_id = request.GET.get('connection_id')

            if not connection_id:
                return JsonResponse({
                    'status': 'error',
                    'message': '缺少连接ID'
                }, status=400)

            # 获取连接记录
            try:
                connection = ConnectionRecord.objects.get(id=connection_id)

                # 验证用户权限
                if connection.user != request.user and not request.user.is_staff:
                    return JsonResponse({
                        'status': 'error',
                        'message': '无权访问此连接'
                    }, status=403)

                # 更新连接状态
                if connection.is_active:
                    connection.is_active = False
                    connection.disconnected_at = timezone.now()
                    connection.save()

                    return JsonResponse({
                        'status': 'success',
                        'message': '已断开连接'
                    })
                else:
                    return JsonResponse({
                        'status': 'warning',
                        'message': '连接已断开'
                    })

            except ConnectionRecord.DoesNotExist:
                return JsonResponse({
                    'status': 'error',
                    'message': '连接不存在'
                }, status=404)

        except Exception as e:
            logger.error(f"断开SSH连接错误: {str(e)}")
            return JsonResponse({
                'status': 'error',
                'message': f'断开连接时出错: {str(e)}'
            }, status=500)


class NativeSSHView(APIView):
    """原生SSH终端视图 - 支持真正的交互式终端"""
    permission_classes = [permissions.IsAuthenticated]

    # 存储SSH连接和通道
    _ssh_connections = {}
    _ssh_channels = {}
    _channel_outputs = {}

    @method_decorator(csrf_exempt)
    def post(self, request, host_id):
        """建立原生SSH连接"""
        try:
            host = Host.objects.get(id=host_id)

            # 检查主机状态 - 允许offline状态的主机尝试连接
            print(f"主机状态: {host.status}")
            if host.status == 'disabled':
                return JsonResponse({
                    'status': 'error',
                    'message': '主机已被禁用，无法连接'
                }, status=400)

            # 创建SSH客户端
            ssh_client = paramiko.SSHClient()
            ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            try:
                # 连接SSH
                if host.auth_type == 'password':
                    ssh_client.connect(
                        hostname=host.public_ip,
                        port=host.port,
                        username=host.username,
                        password=host.password,
                        timeout=10
                    )
                else:
                    if host.passphrase:
                        private_key = paramiko.RSAKey.from_private_key(
                            io.StringIO(host.private_key),
                            password=host.passphrase
                        )
                    else:
                        private_key = paramiko.RSAKey.from_private_key(
                            io.StringIO(host.private_key)
                        )

                    ssh_client.connect(
                        hostname=host.public_ip,
                        port=host.port,
                        username=host.username,
                        pkey=private_key,
                        timeout=10
                    )

                # 创建交互式shell通道
                channel = ssh_client.invoke_shell(
                    term='xterm-256color',
                    width=120,
                    height=30
                )
                channel.settimeout(0.05)  # 减少超时时间，提高响应速度

                # 设置通道为非阻塞模式
                channel.setblocking(0)

                # 创建连接记录
                connection = ConnectionRecord.objects.create(
                    user=request.user,
                    host=host,
                    is_active=True
                )

                # 存储连接和通道
                connection_id = connection.id
                self._ssh_connections[connection_id] = ssh_client
                self._ssh_channels[connection_id] = channel
                self._channel_outputs[connection_id] = []

                return JsonResponse({
                    'status': 'success',
                    'connection_id': connection_id,
                    'message': 'SSH连接成功'
                })

            except Exception as e:
                ssh_client.close()
                return JsonResponse({
                    'status': 'error',
                    'message': f'SSH连接失败: {str(e)}'
                }, status=400)

        except Host.DoesNotExist:
            return JsonResponse({
                'status': 'error',
                'message': '主机不存在'
            }, status=404)
        except Exception as e:
            return JsonResponse({
                'status': 'error',
                'message': f'连接失败: {str(e)}'
            }, status=500)

    @method_decorator(csrf_exempt)
    def put(self, request, host_id):
        """发送数据到SSH通道"""
        try:
            data = json.loads(request.body)
            connection_id = data.get('connection_id')
            input_data = data.get('data', '')

            if not connection_id:
                return JsonResponse({
                    'status': 'error',
                    'message': '缺少连接ID'
                }, status=400)

            # 转换connection_id为整数
            try:
                connection_id = int(connection_id)
            except ValueError:
                return JsonResponse({
                    'status': 'error',
                    'message': '连接ID格式错误'
                }, status=400)

            print(f"发送SSH数据 - 主机ID: {host_id}, 连接ID: {connection_id}")

            # 获取SSH通道
            channel = self._ssh_channels.get(connection_id)
            if not channel:
                return JsonResponse({
                    'status': 'error',
                    'message': f'连接不存在或已断开 (连接ID: {connection_id})'
                }, status=404)

            # 发送数据到通道
            if input_data:
                # 检查是否是Tab键（ASCII 9）
                if len(input_data) == 1 and ord(input_data) == 9:
                    # Tab补全：发送Tab键到shell
                    channel.send(input_data)
                    # 等待一下让shell处理补全
                    time.sleep(0.1)
                else:
                    channel.send(input_data)

            # 读取输出 - 优化读取逻辑
            output = ''
            try:
                # 读取标准输出
                while channel.recv_ready():
                    chunk = channel.recv(8192).decode('utf-8', errors='ignore')
                    output += chunk
                    if len(output) > 32768:  # 限制单次输出大小
                        break

                # 读取错误输出
                while channel.recv_stderr_ready():
                    chunk = channel.recv_stderr(4096).decode('utf-8', errors='ignore')
                    output += chunk

            except socket.timeout:
                pass
            except Exception as e:
                print(f"读取SSH输出时出错: {e}")
                pass

            # 存储输出用于后续获取
            if output:
                self._channel_outputs[connection_id].append(output)

            return JsonResponse({
                'status': 'success',
                'output': output
            })

        except Exception as e:
            return JsonResponse({
                'status': 'error',
                'message': f'发送数据失败: {str(e)}'
            }, status=500)

    @method_decorator(csrf_exempt)
    def get(self, request, host_id):
        """获取SSH通道输出"""
        try:
            connection_id = request.GET.get('connection_id')

            if not connection_id:
                return JsonResponse({
                    'status': 'error',
                    'message': '缺少连接ID'
                }, status=400)

            # 转换connection_id为整数
            try:
                connection_id = int(connection_id)
            except ValueError:
                return JsonResponse({
                    'status': 'error',
                    'message': '连接ID格式错误'
                }, status=400)

            print(f"获取SSH输出 - 主机ID: {host_id}, 连接ID: {connection_id}")
            print(f"当前活跃连接: {list(self._ssh_channels.keys())}")

            # 获取SSH通道
            channel = self._ssh_channels.get(connection_id)
            if not channel:
                return JsonResponse({
                    'status': 'error',
                    'message': f'连接不存在或已断开 (连接ID: {connection_id})'
                }, status=404)

            # 读取新的输出 - 优化读取逻辑
            output = ''
            try:
                # 读取标准输出
                while channel.recv_ready():
                    chunk = channel.recv(8192).decode('utf-8', errors='ignore')
                    output += chunk
                    if len(output) > 32768:  # 限制单次输出大小
                        break

                # 读取错误输出
                while channel.recv_stderr_ready():
                    chunk = channel.recv_stderr(4096).decode('utf-8', errors='ignore')
                    output += chunk

            except socket.timeout:
                pass
            except Exception as e:
                print(f"读取SSH输出时出错: {e}")
                pass

            return JsonResponse({
                'status': 'success',
                'output': output
            })

        except Exception as e:
            return JsonResponse({
                'status': 'error',
                'message': f'获取输出失败: {str(e)}'
            }, status=500)

    @method_decorator(csrf_exempt)
    def delete(self, request, host_id):
        """断开SSH连接"""
        try:
            connection_id = request.GET.get('connection_id')

            if not connection_id:
                return JsonResponse({
                    'status': 'error',
                    'message': '缺少连接ID'
                }, status=400)

            # 转换connection_id为整数
            try:
                connection_id = int(connection_id)
            except ValueError:
                return JsonResponse({
                    'status': 'error',
                    'message': '连接ID格式错误'
                }, status=400)

            print(f"断开SSH连接 - 主机ID: {host_id}, 连接ID: {connection_id}")

            # 清理连接
            if connection_id in self._ssh_channels:
                self._ssh_channels[connection_id].close()
                del self._ssh_channels[connection_id]

            if connection_id in self._ssh_connections:
                self._ssh_connections[connection_id].close()
                del self._ssh_connections[connection_id]

            if connection_id in self._channel_outputs:
                del self._channel_outputs[connection_id]

            # 更新数据库记录
            try:
                connection = ConnectionRecord.objects.get(id=connection_id)
                connection.is_active = False
                connection.disconnected_at = timezone.now()
                connection.save()
            except:
                pass

            return JsonResponse({
                'status': 'success',
                'message': '连接已断开'
            })

        except Exception as e:
            return JsonResponse({
                'status': 'error',
                'message': f'断开连接失败: {str(e)}'
            }, status=500)


class UserGroupViewSet(viewsets.ModelViewSet):
    """用户组视图集"""
    queryset = UserGroup.objects.all()
    serializer_class = UserGroupSerializer
    permission_classes = [permissions.IsAuthenticated]

    @action(detail=True, methods=['get'])
    def users(self, request, pk=None):
        """获取组中的所有用户"""
        group = self.get_object()
        user_groups = UserGroupRole.objects.filter(group=group).select_related('user', 'role')
        serializer = UserGroupRoleSerializer(user_groups, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['get'])
    def roles(self, request, pk=None):
        """获取组中定义的所有角色"""
        group = self.get_object()
        roles = UserRole.objects.filter(group=group)
        serializer = SimpleUserRoleSerializer(roles, many=True)
        return Response(serializer.data)

    @action(detail=False, methods=['get'])
    def with_roles(self, request):
        """获取所有组及其对应的角色"""
        groups = UserGroup.objects.all()
        result = []

        for group in groups:
            roles = UserRole.objects.filter(group=group)
            result.append({
                'id': group.id,
                'name': group.name,
                'code': group.code,
                'description': group.description,
                'roles': SimpleUserRoleSerializer(roles, many=True).data
            })

        return Response(result)


class UserRoleViewSet(viewsets.ModelViewSet):
    """用户角色视图集"""
    queryset = UserRole.objects.all()
    serializer_class = UserRoleSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        """允许按组过滤角色"""
        queryset = UserRole.objects.all()
        group_id = self.request.query_params.get('group', None)
        if group_id:
            queryset = queryset.filter(group_id=group_id)
        return queryset

    @action(detail=True, methods=['get'])
    def users(self, request, pk=None):
        """获取拥有此角色的所有用户"""
        role = self.get_object()
        user_roles = UserGroupRole.objects.filter(role=role).select_related('user', 'group')
        serializer = UserGroupRoleSerializer(user_roles, many=True)
        return Response(serializer.data)


class UserGroupRoleViewSet(viewsets.ModelViewSet):
    """用户组角色关联视图集"""
    queryset = UserGroupRole.objects.all()
    serializer_class = UserGroupRoleSerializer
    permission_classes = [permissions.IsAuthenticated, permissions.IsAdminUser]

    def get_queryset(self):
        """允许按用户或组过滤"""
        queryset = UserGroupRole.objects.all()
        user_id = self.request.query_params.get('user', None)
        group_id = self.request.query_params.get('group', None)

        if user_id:
            queryset = queryset.filter(user_id=user_id)
        if group_id:
            queryset = queryset.filter(group_id=group_id)

        return queryset.select_related('user', 'group', 'role')


class MenuItemViewSet(viewsets.ModelViewSet):
    """
    菜单项管理视图集
    """
    queryset = MenuItem.objects.all()
    serializer_class = MenuItemSerializer
    permission_classes = (IsAuthenticated,)

    @action(detail=False, methods=['get'])
    def get_menu_tree(self, request):
        """获取菜单树结构"""
        # 只获取顶级菜单（没有父菜单的项目）
        root_menus = MenuItem.objects.filter(parent=None, is_enabled=True).order_by('order')
        serializer = self.get_serializer(root_menus, many=True)
        return Response(serializer.data)

    @action(detail=False, methods=['get'])
    def get_user_menus(self, request):
        """获取当前用户有权限的菜单"""
        user = request.user

        # 如果用户是超级管理员，返回所有菜单
        if user.is_superuser:
            root_menus = MenuItem.objects.filter(parent=None, is_enabled=True).order_by('order')
            serializer = self.get_serializer(root_menus, many=True)
            return Response(serializer.data)

        # 获取用户所属的用户组
        groups = user.groups.all()

        # 获取这些用户组关联的角色
        group_roles = GroupRole.objects.filter(group__in=groups).select_related('role')
        roles = [gr.role for gr in group_roles]

        if not roles:
            # 如果用户没有任何角色，返回空菜单
            return Response([])

        # 获取这些角色有权限的菜单项ID（至少有读权限）
        menu_ids = RoleMenuPermission.objects.filter(
            role__in=roles,
            can_read=True
        ).values_list('menu_item_id', flat=True).distinct()

        # 获取所有有权限的菜单项
        user_menus = MenuItem.objects.filter(id__in=menu_ids, is_enabled=True)

        # 构建菜单树，只包含有权限的菜单
        def build_menu_tree():
            # 获取所有菜单项（包括父菜单）
            all_menus = MenuItem.objects.filter(is_enabled=True).select_related('parent')
            menu_dict = {menu.id: menu for menu in all_menus}

            # 构建有权限的菜单树
            result_menus = []

            # 首先找出所有顶级菜单
            for menu in all_menus:
                if menu.parent is None:
                    # 检查这个顶级菜单是否有子菜单有权限
                    if has_accessible_children(menu, menu_ids, menu_dict):
                        # 构建这个菜单的子树
                        menu_data = build_menu_with_children(menu, menu_ids, menu_dict)
                        if menu_data:
                            result_menus.append(menu_data)

            return result_menus

        def has_accessible_children(menu, accessible_menu_ids, menu_dict):
            """检查菜单是否有可访问的子菜单"""
            # 如果菜单本身有权限，返回True
            if menu.id in accessible_menu_ids:
                return True

            # 检查子菜单
            for child_menu in menu_dict.values():
                if child_menu.parent_id == menu.id:
                    if has_accessible_children(child_menu, accessible_menu_ids, menu_dict):
                        return True

            return False

        def build_menu_with_children(menu, accessible_menu_ids, menu_dict):
            """构建包含子菜单的菜单数据"""
            # 获取有权限的子菜单
            children = []
            for child_menu in menu_dict.values():
                if child_menu.parent_id == menu.id:
                    if has_accessible_children(child_menu, accessible_menu_ids, menu_dict):
                        child_data = build_menu_with_children(child_menu, accessible_menu_ids, menu_dict)
                        if child_data:
                            children.append(child_data)

            # 如果菜单本身有权限或有可访问的子菜单，则包含它
            if menu.id in accessible_menu_ids or children:
                from .serializers import MenuItemSerializer
                serializer = MenuItemSerializer(menu)
                menu_data = serializer.data
                if children:
                    menu_data['children'] = sorted(children, key=lambda x: x.get('order', 0))
                return menu_data

            return None

        # 构建菜单树
        menu_tree = build_menu_tree()
        return Response(sorted(menu_tree, key=lambda x: x.get('order', 0)))

    @action(detail=False, methods=['get'], permission_classes=[permissions.IsAuthenticated], url_path='permissions')
    def get_user_permissions(self, request):
        """获取当前用户的详细权限信息（用于前端权限控制）"""
        user = request.user

        # 超级管理员拥有所有权限
        if user.is_superuser:
            return Response({
                'is_superuser': True,
                'permissions': []
            })

        # 获取用户所属的用户组
        groups = user.groups.all()

        # 获取这些用户组关联的角色（权限合并策略）
        group_roles = GroupRole.objects.filter(group__in=groups).select_related('role')
        roles = [gr.role for gr in group_roles]

        if not roles:
            return Response({
                'is_superuser': False,
                'permissions': []
            })

        # 获取所有角色的菜单权限（权限合并：取并集）
        role_permissions = RoleMenuPermission.objects.filter(role__in=roles).select_related('menu_item')

        # 按菜单路径合并权限
        permission_dict = {}
        for perm in role_permissions:
            menu_path = perm.menu_item.path
            if menu_path not in permission_dict:
                permission_dict[menu_path] = {
                    'menu_path': menu_path,
                    'can_read': False,
                    'can_write': False
                }

            # 权限合并：只要有一个角色有权限，用户就有权限
            if perm.can_read:
                permission_dict[menu_path]['can_read'] = True
            if perm.can_write:
                permission_dict[menu_path]['can_write'] = True

        permissions_list = list(permission_dict.values())

        return Response({
            'is_superuser': False,
            'permissions': permissions_list
        })


class GroupMenuPermissionViewSet(viewsets.ModelViewSet):
    """
    用户组菜单权限管理视图集
    """
    queryset = GroupMenuPermission.objects.all()
    serializer_class = GroupMenuPermissionSerializer
    permission_classes = (IsAuthenticated,)

    @action(detail=False, methods=['get'])
    def get_group_permissions(self, request):
        """获取指定用户组的菜单权限"""
        group_id = request.query_params.get('group_id')
        if not group_id:
            return Response({"error": "必须提供group_id参数"}, status=status.HTTP_400_BAD_REQUEST)

        permissions = GroupMenuPermission.objects.filter(group_id=group_id)
        serializer = self.get_serializer(permissions, many=True)
        return Response(serializer.data)

    @action(detail=False, methods=['post'])
    def batch_update_permissions(self, request):
        """批量更新用户组的菜单权限"""
        group_id = request.data.get('group_id')
        menu_ids = request.data.get('menu_ids', [])

        if not group_id:
            return Response({"error": "必须提供group_id参数"}, status=status.HTTP_400_BAD_REQUEST)

        try:
            group = Group.objects.get(id=group_id)
        except Group.DoesNotExist:
            return Response({"error": "指定的用户组不存在"}, status=status.HTTP_404_NOT_FOUND)

        # 删除当前所有权限
        GroupMenuPermission.objects.filter(group_id=group_id).delete()

        # 批量创建新权限
        new_permissions = []
        for menu_id in menu_ids:
            try:
                menu_item = MenuItem.objects.get(id=menu_id)
                new_permissions.append(GroupMenuPermission(group=group, menu_item=menu_item))
            except MenuItem.DoesNotExist:
                # 忽略不存在的菜单项
                continue

        if new_permissions:
            GroupMenuPermission.objects.bulk_create(new_permissions)

        return Response({"message": f"成功更新 {len(new_permissions)} 个菜单权限"}, status=status.HTTP_200_OK)

    @action(detail=False, methods=['get'])
    def check_permission(self, request):
        """检查当前用户是否有权限访问指定菜单路径"""
        path = request.query_params.get('path')
        permission_type = request.query_params.get('permission_type', 'read')  # read 或 write

        if not path:
            return Response({"error": "必须提供path参数"}, status=status.HTTP_400_BAD_REQUEST)

        user = request.user

        # 如果是超级管理员，直接返回有权限
        if user.is_superuser:
            return Response({"has_permission": True, "reason": "超级管理员"})

        # 获取用户所属的用户组
        groups = user.groups.all()

        # 尝试找到匹配的菜单项
        try:
            menu_item = MenuItem.objects.get(path=path)
        except MenuItem.DoesNotExist:
            return Response({"has_permission": False, "error": "菜单路径不存在"})

        # 获取用户组关联的角色
        group_roles = GroupRole.objects.filter(group__in=groups).select_related('role')

        # 检查是否有权限
        for group_role in group_roles:
            role = group_role.role
            if role.has_menu_permission(menu_item, permission_type):
                return Response({
                    "has_permission": True,
                    "reason": f"通过角色 {role.name} 获得权限"
                })

        return Response({
            "has_permission": False,
            "reason": "没有相应权限"
        })


class GroupViewSet(viewsets.ModelViewSet):
    """
    Django用户组管理视图集
    """
    queryset = Group.objects.all()
    serializer_class = GroupSerializer
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        """获取用户组列表，包含统计信息"""
        queryset = Group.objects.all().order_by('name')
        search = self.request.query_params.get('search', None)
        if search:
            queryset = queryset.filter(name__icontains=search)
        return queryset

    def list(self, request, *args, **kwargs):
        """重写列表方法，添加统计信息"""
        queryset = self.get_queryset()
        serializer = self.get_serializer(queryset, many=True)

        # 为每个用户组添加统计信息
        data = serializer.data
        for group_data in data:
            group_id = group_data['id']
            group = Group.objects.get(id=group_id)

            # 统计成员数量
            group_data['user_count'] = group.user_set.count()

            # 统计关联的角色数量
            group_data['role_count'] = GroupRole.objects.filter(group=group).count()

            # 获取关联的角色信息
            group_roles = GroupRole.objects.filter(group=group).select_related('role')
            group_data['roles'] = [
                {
                    'id': gr.role.id,
                    'name': gr.role.name,
                    'is_system': gr.role.is_system
                }
                for gr in group_roles
            ]

        return Response(data)

    @action(detail=True, methods=['get'])
    def members(self, request, pk=None):
        """获取用户组成员"""
        group = self.get_object()
        users = group.user_set.all()
        serializer = UserSerializer(users, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def add_members(self, request, pk=None):
        """添加用户组成员"""
        group = self.get_object()
        user_ids = request.data.get('user_ids', [])

        if not user_ids:
            return Response(
                {'error': '用户ID列表不能为空'},
                status=status.HTTP_400_BAD_REQUEST
            )

        try:
            users = User.objects.filter(id__in=user_ids)
            for user in users:
                group.user_set.add(user)

            return Response({
                'message': f'成功添加 {len(users)} 个用户到组 {group.name}'
            })
        except Exception as e:
            return Response(
                {'error': f'添加成员失败: {str(e)}'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

    @action(detail=True, methods=['post'])
    def remove_members(self, request, pk=None):
        """移除用户组成员"""
        group = self.get_object()
        user_ids = request.data.get('user_ids', [])

        if not user_ids:
            return Response(
                {'error': '用户ID列表不能为空'},
                status=status.HTTP_400_BAD_REQUEST
            )

        try:
            users = User.objects.filter(id__in=user_ids)
            for user in users:
                group.user_set.remove(user)

            return Response({
                'message': f'成功从组 {group.name} 移除 {len(users)} 个用户'
            })
        except Exception as e:
            return Response(
                {'error': f'移除成员失败: {str(e)}'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

    @action(detail=True, methods=['get'])
    def roles(self, request, pk=None):
        """获取用户组关联的角色"""
        group = self.get_object()
        group_roles = GroupRole.objects.filter(group=group).select_related('role')
        serializer = GroupRoleSerializer(group_roles, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def assign_roles(self, request, pk=None):
        """为用户组分配角色"""
        group = self.get_object()
        role_ids = request.data.get('role_ids', [])

        try:
            # 清除现有角色关联
            GroupRole.objects.filter(group=group).delete()

            # 如果role_ids为空，则只清除角色关联，不创建新的
            if role_ids:
                # 创建新的角色关联
                roles = Role.objects.filter(id__in=role_ids)
                group_roles = []
                for role in roles:
                    group_roles.append(GroupRole(group=group, role=role))

                GroupRole.objects.bulk_create(group_roles)

                return Response({
                    'message': f'成功为组 {group.name} 分配 {len(roles)} 个角色'
                })
            else:
                return Response({
                    'message': f'成功清除组 {group.name} 的所有角色关联'
                })
        except Exception as e:
            return Response(
                {'error': f'分配角色失败: {str(e)}'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


# 添加Ansible Inventory相关视图
# Ansible相关视图已迁移到ansible_management应用

class SystemExecuteView(APIView):
    """
    执行系统命令的API视图
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        """
        执行系统命令并返回结果
        """
        command = request.data.get('command')
        if not command:
            return Response({"error": "命令不能为空"}, status=status.HTTP_400_BAD_REQUEST)

        logger.info(f"用户 {request.user.username} 执行系统命令: {command}")

        try:
            # 特殊处理sudo命令，确保不需要交互式输入密码
            if 'sudo' in command:
                # 使用NOPASSWD方式执行sudo命令
                # 注意：这需要在sudoers文件中配置相应的权限
                # 例如：www-data ALL=(ALL) NOPASSWD: /bin/cat /etc/ansible/hosts, /usr/bin/tee /etc/ansible/hosts
                logger.info(f"检测到sudo命令，使用特殊处理: {command}")

            # 执行命令并获取输出
            result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)

            # 返回命令执行结果
            return Response({
                "output": result.stdout,
                "error": result.stderr,
                "return_code": result.returncode
            })
        except subprocess.TimeoutExpired:
            return Response({"error": "命令执行超时"}, status=status.HTTP_408_REQUEST_TIMEOUT)
        except Exception as e:
            logger.error(f"执行命令时发生错误: {str(e)}")
            return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

class SimpleFileView(APIView):
    """
    简单的文件读写API
    """
    permission_classes = [IsAuthenticated]

    def get(self, request):
        """
        读取文件内容
        """
        try:
            file_path = request.query_params.get('file_path', '/etc/ansible/hosts')
            logger.info(f"用户 {request.user.username} 读取文件: {file_path}")

            # 直接使用os模块读取文件
            if os.path.exists(file_path):
                with open(file_path, 'r') as f:
                    content = f.read()
                return Response({
                    "status": "success",
                    "content": content,
                    "file_path": file_path
                })
            else:
                return Response({
                    "status": "error",
                    "error": f"文件不存在: {file_path}"
                }, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            logger.error(f"读取文件时发生错误: {str(e)}")
            return Response({
                "status": "error",
                "error": str(e)
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    def post(self, request):
        """
        写入文件内容
        """
        try:
            file_path = request.data.get('file_path', '/etc/ansible/hosts')
            content = request.data.get('content')

            if content is None:
                return Response({
                    "status": "error",
                    "error": "缺少文件内容"
                }, status=status.HTTP_400_BAD_REQUEST)

            logger.info(f"用户 {request.user.username} 写入文件: {file_path}")

            # 使用临时文件和sudo命令写入
            temp_file = f"/tmp/file_temp_{int(time.time())}"
            with open(temp_file, 'w') as f:
                f.write(content)

            # 使用sudo命令将临时文件内容写入目标文件
            cmd = f"sudo cp {temp_file} {file_path} && rm {temp_file}"
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True)

            if result.returncode == 0:
                return Response({
                    "status": "success",
                    "message": f"文件已成功保存到 {file_path}"
                })
            else:
                return Response({
                    "status": "error",
                    "error": f"保存文件失败: {result.stderr}"
                }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        except Exception as e:
            logger.error(f"写入文件时发生错误: {str(e)}")
            return Response({
                "status": "error",
                "error": str(e)
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


# SSH审计相关API视图
class SSHSessionListView(APIView):
    """SSH会话列表API"""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        """获取SSH会话列表"""
        try:
            # 获取查询参数
            page = int(request.GET.get('page', 1))
            page_size = int(request.GET.get('page_size', 20))
            user_filter = request.GET.get('user', '')
            host_filter = request.GET.get('host', '')
            status_filter = request.GET.get('status', '')
            start_date = request.GET.get('start_date', '')
            end_date = request.GET.get('end_date', '')

            # 构建查询
            queryset = SSHSession.objects.all().select_related('user', 'host')

            # 应用过滤器
            if user_filter:
                queryset = queryset.filter(user__username__icontains=user_filter)
            if host_filter:
                queryset = queryset.filter(host__hostname__icontains=host_filter)
            if status_filter:
                queryset = queryset.filter(status=status_filter)
            if start_date:
                queryset = queryset.filter(start_time__gte=start_date)
            if end_date:
                queryset = queryset.filter(start_time__lte=end_date)

            # 排序
            queryset = queryset.order_by('-start_time')

            # 分页
            total = queryset.count()
            start = (page - 1) * page_size
            end = start + page_size
            sessions = queryset[start:end]

            # 序列化数据
            session_list = []
            for session in sessions:
                duration_display = self._format_duration(session.duration)

                session_data = {
                    'session_id': session.session_id,
                    'user_name': session.user.username,
                    'host_name': session.host.hostname,
                    'host_ip': session.host.public_ip or session.host.private_ip or session.host.ip,
                    'client_ip': session.client_ip,
                    'start_time': session.start_time.isoformat() if session.start_time else None,
                    'end_time': session.end_time.isoformat() if session.end_time else None,
                    'duration_display': duration_display,
                    'command_count': session.command_count or 0,
                    'status': session.status,
                    'status_display': session.get_status_display(),
                    'data_size': session.data_size or 0,
                    'user_agent': session.user_agent
                }
                session_list.append(session_data)

            return Response({
                'results': session_list,
                'count': total,
                'page': page,
                'page_size': page_size,
                'total_pages': (total + page_size - 1) // page_size
            })

        except Exception as e:
            logger.error(f"获取SSH会话列表失败: {str(e)}")
            return Response({
                'detail': f'获取会话列表失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    def _format_duration(self, duration_seconds):
        """格式化持续时间"""
        if not duration_seconds:
            return '0秒'

        hours = int(duration_seconds // 3600)
        minutes = int((duration_seconds % 3600) // 60)
        seconds = int(duration_seconds % 60)

        if hours > 0:
            return f'{hours}小时{minutes}分钟'
        elif minutes > 0:
            return f'{minutes}分钟{seconds}秒'
        else:
            return f'{seconds}秒'


class SSHSessionReplayView(APIView):
    """SSH会话回放API"""
    permission_classes = [IsAuthenticated]

    def get(self, request, session_id):
        """获取会话回放数据"""
        # 立即打印到控制台，确保能看到
        print(f"🔍🔍🔍 回放API被调用！会话ID: {session_id}, 用户: {request.user.username}")

        try:
            logger.info(f"🔍 回放API被调用，会话ID: {session_id}, 用户: {request.user.username}")
            from .jumpserver_audit import jumpserver_audit

            # 获取会话完整数据
            session_data = jumpserver_audit.get_session_replay_data(session_id)
            logger.info(f"🔍 回放数据获取结果: {session_data is not None}")
            print(f"🔍🔍🔍 回放数据获取结果: {session_data is not None}")

            if not session_data:
                logger.warning(f"⚠️ 会话不存在或无法访问: {session_id}")
                print(f"⚠️⚠️⚠️ 会话不存在或无法访问: {session_id}")
                return Response({
                    'error': '会话不存在或无法访问'
                }, status=status.HTTP_404_NOT_FOUND)

            logger.info(f"✅ 回放数据返回成功，数据大小: {len(str(session_data))}")
            print(f"✅✅✅ 回放数据返回成功，数据大小: {len(str(session_data))}")
            return Response(session_data)

        except Exception as e:
            logger.error(f"❌ 获取会话回放数据失败: {str(e)}")
            print(f"❌❌❌ 获取会话回放数据失败: {str(e)}")
            import traceback
            logger.error(f"❌ 错误详情: {traceback.format_exc()}")
            print(f"❌❌❌ 错误详情: {traceback.format_exc()}")
            return Response({
                'error': str(e)
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class SSHCommandListView(APIView):
    """SSH命令列表API"""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        """获取SSH命令列表"""
        try:
            # 获取查询参数
            page = int(request.GET.get('page', 1))
            page_size = int(request.GET.get('page_size', 20))
            session_id = request.GET.get('session_id', '')
            command_filter = request.GET.get('command', '')
            user_filter = request.GET.get('user', '')
            host_filter = request.GET.get('host', '')
            risk_level = request.GET.get('risk_level', '')
            is_dangerous = request.GET.get('is_dangerous', '')

            # 构建查询
            queryset = SSHCommand.objects.all().select_related('session__user', 'session__host')

            # 应用过滤器
            if session_id:
                queryset = queryset.filter(session__session_id=session_id)
            if command_filter:
                queryset = queryset.filter(command__icontains=command_filter)
            if user_filter:
                queryset = queryset.filter(session__user__username__icontains=user_filter)
            if host_filter:
                queryset = queryset.filter(session__host__hostname__icontains=host_filter)
            if risk_level:
                queryset = queryset.filter(risk_level=risk_level)
            if is_dangerous:
                queryset = queryset.filter(is_dangerous=(is_dangerous.lower() == 'true'))

            # 只显示输入命令
            queryset = queryset.filter(command_type='input')

            # 排序
            queryset = queryset.order_by('-timestamp')

            # 分页
            total = queryset.count()
            start = (page - 1) * page_size
            end = start + page_size
            commands = queryset[start:end]

            # 序列化数据
            command_list = []
            for cmd in commands:
                command_data = {
                    'id': cmd.id,
                    'session_info': {
                        'session_id': cmd.session.session_id,
                        'user_name': cmd.session.user.username,
                        'host_name': cmd.session.host.hostname,
                        'host_ip': cmd.session.host.public_ip or cmd.session.host.private_ip or cmd.session.host.ip
                    },
                    'command': cmd.command,
                    'timestamp': cmd.timestamp.isoformat(),
                    'duration': cmd.duration,
                    'exit_code': cmd.exit_code,
                    'risk_level': cmd.risk_level,
                    'risk_level_display': cmd.get_risk_level_display(),
                    'is_dangerous': cmd.is_dangerous,
                    'working_directory': cmd.working_directory,
                    'output': cmd.output[:500] + '...' if len(cmd.output) > 500 else cmd.output  # 截断长输出
                }
                command_list.append(command_data)

            return Response({
                'results': command_list,
                'count': total,
                'page': page,
                'page_size': page_size,
                'total_pages': (total + page_size - 1) // page_size
            })

        except Exception as e:
            logger.error(f"获取SSH命令列表失败: {str(e)}")
            return Response({
                'detail': f'获取命令列表失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class SSHAccessControlView(APIView):
    """SSH访问控制视图"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        """请求SSH访问令牌"""
        try:
            host_id = request.data.get('host_id')
            if not host_id:
                return Response({
                    'success': False,
                    'message': '主机ID不能为空'
                }, status=status.HTTP_400_BAD_REQUEST)

            # 验证主机是否存在
            try:
                host = Host.objects.get(id=host_id)
            except Host.DoesNotExist:
                return Response({
                    'success': False,
                    'message': '主机不存在'
                }, status=status.HTTP_404_NOT_FOUND)

            # 检查用户是否有权限访问此主机
            if not self._check_host_permission(request.user, host):
                return Response({
                    'success': False,
                    'message': '无权访问此主机'
                }, status=status.HTTP_403_FORBIDDEN)

            # 生成访问令牌
            access_token = self._generate_access_token(request.user, host)

            # 记录访问请求
            self._log_access_request(request.user, host, access_token)

            return Response({
                'success': True,
                'access_token': access_token,
                'expires_in': 300,  # 5分钟有效期
                'message': '访问令牌已生成'
            })

        except Exception as e:
            logger.error(f"生成SSH访问令牌失败: {str(e)}")
            return Response({
                'success': False,
                'message': '生成访问令牌失败'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    def _check_host_permission(self, user, host):
        """检查用户是否有权限访问主机"""
        # 管理员可以访问所有主机
        if user.is_superuser:
            return True

        # 这里可以根据实际需求实现更复杂的权限控制
        # 例如：基于用户组、角色、主机标签等

        # 目前简单实现：所有已认证用户都可以访问
        return user.is_authenticated

    def _generate_access_token(self, user, host):
        """生成访问令牌"""
        import secrets
        import hashlib
        import time

        # 生成随机令牌
        random_token = secrets.token_urlsafe(32)

        # 添加时间戳和用户/主机信息
        timestamp = str(int(time.time()))
        token_data = f"{user.id}:{host.id}:{timestamp}:{random_token}"

        # 生成最终令牌
        token_hash = hashlib.sha256(token_data.encode()).hexdigest()

        # 存储令牌到缓存或数据库（这里简化处理）
        from django.core.cache import cache
        cache_key = f"ssh_access_token:{token_hash}"
        cache.set(cache_key, {
            'user_id': user.id,
            'host_id': host.id,
            'created_at': timestamp
        }, timeout=300)  # 5分钟有效期

        return token_hash

    def _log_access_request(self, user, host, token):
        """记录访问请求"""
        try:
            ConnectionRecord.objects.create(
                user=user,
                host=host,
                is_active=False,  # 只是请求访问，还未实际连接
                connected_at=timezone.now()
            )
        except Exception as e:
            logger.error(f"记录SSH访问请求失败: {str(e)}")


class SSHTokenValidationView(APIView):
    """SSH令牌验证视图"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        """验证SSH访问令牌"""
        try:
            token = request.data.get('token')
            host_id = request.data.get('host_id')

            if not token or not host_id:
                return Response({
                    'valid': False,
                    'message': '令牌或主机ID不能为空'
                }, status=status.HTTP_400_BAD_REQUEST)

            # 验证令牌
            from django.core.cache import cache
            cache_key = f"ssh_access_token:{token}"
            token_data = cache.get(cache_key)

            if not token_data:
                return Response({
                    'valid': False,
                    'message': '令牌无效或已过期'
                }, status=status.HTTP_401_UNAUTHORIZED)

            # 验证用户和主机匹配
            if (token_data['user_id'] != request.user.id or
                token_data['host_id'] != int(host_id)):
                return Response({
                    'valid': False,
                    'message': '令牌与用户或主机不匹配'
                }, status=status.HTTP_403_FORBIDDEN)

            return Response({
                'valid': True,
                'message': '令牌验证成功'
            })

        except Exception as e:
            logger.error(f"验证SSH访问令牌失败: {str(e)}")
            return Response({
                'valid': False,
                'message': '令牌验证失败'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


# ==================== 角色权限系统视图 ====================

class RoleViewSet(viewsets.ModelViewSet):
    """角色管理视图集"""
    queryset = Role.objects.all()
    serializer_class = RoleSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        """获取角色列表，支持搜索"""
        queryset = Role.objects.all().order_by('name')
        search = self.request.query_params.get('search', None)
        if search:
            queryset = queryset.filter(
                Q(name__icontains=search) | Q(description__icontains=search)
            )
        return queryset

    def destroy(self, request, *args, **kwargs):
        """删除角色，系统预设角色不能删除"""
        instance = self.get_object()
        if instance.is_system:
            return Response(
                {'error': '系统预设角色不能删除'},
                status=status.HTTP_400_BAD_REQUEST
            )
        return super().destroy(request, *args, **kwargs)

    @action(detail=True, methods=['get'])
    def permissions(self, request, pk=None):
        """获取角色的菜单权限"""
        role = self.get_object()
        permissions = RoleMenuPermission.objects.filter(role=role).select_related('menu_item')
        serializer = RoleMenuPermissionSerializer(permissions, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def set_permissions(self, request, pk=None):
        """设置角色的菜单权限"""
        print(f"🚀 开始设置权限 - 请求URL: {request.path}")
        print(f"🚀 请求方法: {request.method}")
        print(f"🚀 查询参数: {request.query_params}")
        print(f"🚀 请求数据: {request.data}")

        try:
            print(f"🔧 获取角色对象...")
            role = self.get_object()
            print(f"🔧 角色获取成功: {role.name}")

            menu_permissions = request.data.get('menu_permissions', [])
            print(f"🔧 设置权限 - 角色: {role.name}")
            print(f"🔧 设置权限 - 权限数据: {menu_permissions}")

            print(f"🗑️ 开始删除现有权限...")
            # 清除现有权限
            deleted_count = RoleMenuPermission.objects.filter(role=role).delete()[0]
            print(f"🗑️ 删除现有权限: {deleted_count} 条")

            # 创建新权限
            created_count = 0
            for perm_data in menu_permissions:
                menu_id = perm_data.get('menu_id')
                can_read = perm_data.get('can_read', True)
                can_write = perm_data.get('can_write', False)

                print(f"🔍 处理菜单ID: {menu_id} (类型: {type(menu_id)})")

                if menu_id:
                    try:
                        menu_item = None

                        # 支持数字ID和路径两种方式
                        if isinstance(menu_id, str) and menu_id.startswith('/'):
                            # 如果是路径，按路径查找或创建
                            print(f"🔍 按路径查找菜单: {menu_id}")
                            try:
                                menu_item = MenuItem.objects.get(path=menu_id)
                                print(f"✅ 通过路径找到菜单: {menu_id} -> {menu_item.name}")
                            except MenuItem.DoesNotExist:
                                # 如果菜单不存在，创建一个最小化的菜单项
                                print(f"🔧 菜单不存在，创建最小化菜单项: {menu_id}")
                                menu_name = menu_id.split('/')[-1] or 'Root'
                                # 从静态菜单配置中获取更好的名称
                                static_menu_names = {
                                    '/dashboard': '仪表盘',
                                    '/cmdb': 'CMDB资产管理',
                                    '/cmdb/hosts': '主机管理',
                                    '/cmdb/hostgroups': '主机组管理',
                                    '/cmdb/hosttypes': '主机类型管理',
                                    '/cmdb/connections': '连接管理',
                                    '/ansible': 'Ansible自动化',
                                    '/ansible/config': '配置管理',
                                    '/ansible/inventory': '清单管理',
                                    '/ansible/adhoc': '临时命令',
                                    '/ansible/playbook': 'Playbook管理',
                                    '/ansible/credentials': '凭据管理',
                                    '/ansible/monitor': '监控管理',
                                    '/audit': '审计日志',
                                    '/history/ssh-audit': 'SSH审计',
                                    '/history/ansible-audit': 'Ansible审计',
                                    '/history/sql-audit': 'SQL审计',
                                    '/user': '用户管理',
                                    '/cmdb/user/management': '用户管理',
                                    '/cmdb/user/groups': '用户组管理',
                                    '/cmdb/user/roles': '角色管理',
                                    '/cmdb/user/menu-permission': '菜单权限管理',
                                    '/system': '系统管理',
                                    '/system/menu-management': '菜单管理'
                                }
                                menu_name = static_menu_names.get(menu_id, menu_name)

                                menu_item = MenuItem.objects.create(
                                    name=menu_name,
                                    path=menu_id,
                                    icon='fa-folder',
                                    order=999,  # 默认排序
                                    is_enabled=True
                                )
                                print(f"✅ 创建菜单项成功: {menu_item.name} -> {menu_item.path}")
                        else:
                            # 如果是数字ID，按ID查找
                            try:
                                menu_id_int = int(menu_id)
                                print(f"🔍 按ID查找菜单: {menu_id_int}")
                                menu_item = MenuItem.objects.get(id=menu_id_int)
                                print(f"✅ 通过ID找到菜单: {menu_id_int} -> {menu_item.name}")
                            except (ValueError, TypeError) as ve:
                                print(f"❌ 无效的菜单ID: {menu_id}, 错误: {ve}")
                                continue

                        if menu_item:
                            print(f"🔧 创建权限记录: {role.name} -> {menu_item.name} (读:{can_read}, 写:{can_write})")
                            RoleMenuPermission.objects.create(
                                role=role,
                                menu_item=menu_item,
                                can_read=can_read,
                                can_write=can_write
                            )
                            created_count += 1
                            print(f"✅ 权限创建成功")

                    except MenuItem.DoesNotExist:
                        print(f"❌ 菜单不存在: {menu_id}")
                        continue
                    except Exception as e:
                        print(f"❌ 创建权限失败: {menu_id}, 错误: {e}")
                        import traceback
                        traceback.print_exc()
                        continue

            print(f"🎉 成功创建 {created_count} 个权限")
            return Response({'message': '权限设置成功', 'created_count': created_count})

        except Exception as e:
            print(f"💥 设置权限失败: {e}")
            import traceback
            traceback.print_exc()
            return Response(
                {'error': f'设置权限失败: {str(e)}'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

    @action(detail=True, methods=['get'])
    def groups(self, request, pk=None):
        """获取使用此角色的用户组"""
        role = self.get_object()
        group_roles = GroupRole.objects.filter(role=role).select_related('group')
        serializer = GroupRoleSerializer(group_roles, many=True)
        return Response(serializer.data)


class GroupRoleViewSet(viewsets.ModelViewSet):
    """用户组角色关联视图集"""
    queryset = GroupRole.objects.all()
    serializer_class = GroupRoleSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        """获取用户组角色关联列表"""
        queryset = GroupRole.objects.all().select_related('group', 'role')
        group_id = self.request.query_params.get('group_id', None)
        role_id = self.request.query_params.get('role_id', None)

        if group_id:
            queryset = queryset.filter(group_id=group_id)
        if role_id:
            queryset = queryset.filter(role_id=role_id)

        return queryset.order_by('group__name', 'role__name')


class RoleMenuPermissionViewSet(viewsets.ModelViewSet):
    """角色菜单权限视图集"""
    queryset = RoleMenuPermission.objects.all()
    serializer_class = RoleMenuPermissionSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        """获取角色菜单权限列表"""
        queryset = RoleMenuPermission.objects.all().select_related('role', 'menu_item')
        role_id = self.request.query_params.get('role_id', None)
        menu_id = self.request.query_params.get('menu_id', None)

        if role_id:
            try:
                queryset = queryset.filter(role_id=int(role_id))
            except (ValueError, TypeError):
                # 如果role_id不是有效数字，返回空查询集
                return queryset.none()

        if menu_id:
            try:
                # 尝试将menu_id转换为整数
                menu_id_int = int(menu_id)
                queryset = queryset.filter(menu_item_id=menu_id_int)
            except (ValueError, TypeError):
                # 如果menu_id不是数字，可能是路径，按路径查找
                if isinstance(menu_id, str) and menu_id.startswith('/'):
                    try:
                        menu_item = MenuItem.objects.get(path=menu_id)
                        queryset = queryset.filter(menu_item_id=menu_item.id)
                    except MenuItem.DoesNotExist:
                        # 如果路径对应的菜单不存在，返回空查询集
                        return queryset.none()
                else:
                    # 无效的menu_id，返回空查询集
                    return queryset.none()

        return queryset.order_by('role__name', 'menu_item__order')

    @action(detail=False, methods=['post'])
    def batch_update_permissions(self, request):
        """批量更新角色菜单权限"""
        role_id = request.data.get('role_id')
        menu_permissions = request.data.get('menu_permissions', [])

        if not role_id:
            return Response(
                {'error': '角色ID不能为空'},
                status=status.HTTP_400_BAD_REQUEST
            )

        try:
            role = Role.objects.get(id=role_id)
        except Role.DoesNotExist:
            return Response(
                {'error': '角色不存在'},
                status=status.HTTP_404_NOT_FOUND
            )

        # 清除现有权限
        RoleMenuPermission.objects.filter(role=role).delete()

        # 创建新权限
        created_count = 0
        for perm_data in menu_permissions:
            menu_id = perm_data.get('menu_id')
            can_read = perm_data.get('can_read', True)
            can_write = perm_data.get('can_write', False)

            if menu_id:
                try:
                    # 支持数字ID和路径两种方式
                    if isinstance(menu_id, str) and menu_id.startswith('/'):
                        # 如果是路径，按路径查找或创建
                        try:
                            menu_item = MenuItem.objects.get(path=menu_id)
                        except MenuItem.DoesNotExist:
                            # 如果菜单不存在，创建一个最小化的菜单项
                            menu_name = menu_id.split('/')[-1] or 'Root'
                            # 从静态菜单配置中获取更好的名称
                            static_menu_names = {
                                '/dashboard': '仪表盘',
                                '/cmdb': 'CMDB资产管理',
                                '/cmdb/hosts': '主机管理',
                                '/cmdb/hostgroups': '主机组管理',
                                '/cmdb/hosttypes': '主机类型管理',
                                '/cmdb/connections': '连接管理',
                                '/ansible': 'Ansible自动化',
                                '/ansible/config': '配置管理',
                                '/ansible/inventory': '清单管理',
                                '/ansible/adhoc': '临时命令',
                                '/ansible/playbook': 'Playbook管理',
                                '/ansible/credentials': '凭据管理',
                                '/ansible/monitor': '监控管理',
                                '/audit': '审计日志',
                                '/history/ssh-audit': 'SSH审计',
                                '/history/ansible-audit': 'Ansible审计',
                                '/history/sql-audit': 'SQL审计',
                                '/user': '用户管理',
                                '/cmdb/user/management': '用户管理',
                                '/cmdb/user/groups': '用户组管理',
                                '/cmdb/user/roles': '角色管理',
                                '/cmdb/user/menu-permission': '菜单权限管理',
                                '/system': '系统管理',
                                '/system/menu-management': '菜单管理'
                            }
                            menu_name = static_menu_names.get(menu_id, menu_name)

                            menu_item = MenuItem.objects.create(
                                name=menu_name,
                                path=menu_id,
                                icon='fa-folder',
                                order=999,  # 默认排序
                                is_enabled=True
                            )
                    else:
                        # 如果是数字ID，按ID查找
                        menu_id_int = int(menu_id)
                        menu_item = MenuItem.objects.get(id=menu_id_int)

                    RoleMenuPermission.objects.create(
                        role=role,
                        menu_item=menu_item,
                        can_read=can_read,
                        can_write=can_write
                    )
                    created_count += 1
                except (MenuItem.DoesNotExist, ValueError, TypeError):
                    continue

        return Response({
            'message': f'成功设置 {created_count} 个菜单权限',
            'created_count': created_count
        })


class MenuPermissionManagementView(APIView):
    """菜单权限管理视图"""
    permission_classes = [permissions.IsAuthenticated]

    @action(detail=False, methods=['get'])
    def get_menu_tree(self, request):
        """获取菜单权限树"""
        role_id = request.query_params.get('role_id', None)

        # 获取根菜单
        root_menus = MenuItem.objects.filter(parent=None, is_enabled=True).order_by('order')

        # 序列化菜单树
        serializer = MenuPermissionTreeSerializer(
            root_menus,
            many=True,
            context={'role_id': role_id}
        )

        return Response(serializer.data)

    @action(detail=False, methods=['get'])
    def get_groups_with_roles(self, request):
        """获取带角色信息的用户组列表"""
        groups = Group.objects.all().order_by('name')
        serializer = GroupWithRolesSerializer(groups, many=True)
        return Response(serializer.data)

    @action(detail=False, methods=['post'])
    def check_user_permission(self, request):
        """检查用户权限"""
        user = request.user
        menu_path = request.data.get('menu_path')
        permission_type = request.data.get('permission_type', 'read')  # read 或 write

        if not menu_path:
            return Response(
                {'error': '菜单路径不能为空'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # 超级管理员直接通过
        if user.is_superuser:
            return Response({
                'has_permission': True,
                'reason': '超级管理员'
            })

        try:
            # 查找菜单项
            menu_item = MenuItem.objects.get(path=menu_path)

            # 获取用户的所有角色
            user_groups = user.groups.all()
            group_roles = GroupRole.objects.filter(group__in=user_groups).select_related('role')

            # 检查是否有权限
            for group_role in group_roles:
                role = group_role.role
                if role.has_menu_permission(menu_item, permission_type):
                    return Response({
                        'has_permission': True,
                        'reason': f'通过角色 {role.name} 获得权限'
                    })

            return Response({
                'has_permission': False,
                'reason': '没有相应权限'
            })

        except MenuItem.DoesNotExist:
            return Response({
                'has_permission': False,
                'reason': '菜单项不存在'
            })


class MenuPermissionViewSet(viewsets.ModelViewSet):
    """
    菜单权限管理视图集
    """
    queryset = RoleMenuPermission.objects.all()
    serializer_class = RoleMenuPermissionSerializer
    permission_classes = (IsAuthenticated,)

    @action(detail=False, methods=['get'])
    def get_user_permissions(self, request):
        """获取当前用户的所有菜单权限"""
        try:
            user = request.user

            # 获取用户所属的所有用户组
            user_groups = user.groups.all()

            # 获取用户组关联的所有角色
            group_roles = GroupRole.objects.filter(group__in=user_groups).select_related('role')
            roles = [gr.role for gr in group_roles]

            # 如果用户是超级管理员，获取超级管理员角色
            if user.is_superuser:
                try:
                    admin_role = Role.objects.get(name='超级管理员')
                    if admin_role not in roles:
                        roles.append(admin_role)
                except Role.DoesNotExist:
                    pass

            # 获取所有角色的菜单权限
            all_permissions = {}
            for role in roles:
                role_permissions = RoleMenuPermission.objects.filter(role=role).select_related('menu_item')
                for perm in role_permissions:
                    menu_path = perm.menu_item.path

                    # 如果已存在该菜单的权限，取最大权限（或运算）
                    if menu_path in all_permissions:
                        all_permissions[menu_path]['can_read'] = all_permissions[menu_path]['can_read'] or perm.can_read
                        all_permissions[menu_path]['can_write'] = all_permissions[menu_path]['can_write'] or perm.can_write
                    else:
                        all_permissions[menu_path] = {
                            'menu_path': menu_path,
                            'menu_name': perm.menu_item.name,
                            'menu_icon': perm.menu_item.icon,
                            'can_read': perm.can_read,
                            'can_write': perm.can_write,
                            'role_name': role.name
                        }

            # 转换为列表格式
            permissions_list = list(all_permissions.values())

            return Response({
                'user_id': user.id,
                'username': user.username,
                'is_superuser': user.is_superuser,
                'groups': [group.name for group in user_groups],
                'roles': [role.name for role in roles],
                'permissions': permissions_list
            })

        except Exception as e:
            return Response(
                {'error': f'获取用户权限失败: {str(e)}'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


class SessionCommandsView(APIView):
    """会话命令查询API"""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        """获取指定会话的命令列表"""
        session_id = request.query_params.get('session_id')

        if not session_id:
            return Response(
                {'error': '缺少session_id参数'},
                status=status.HTTP_400_BAD_REQUEST
            )

        try:
            # 使用JumpServer审计服务获取完整数据
            from .jumpserver_audit import jumpserver_audit
            full_data = jumpserver_audit.get_session_replay_data(session_id)

            if full_data:
                return Response(full_data)
            else:
                return Response(
                    {'error': '会话不存在'},
                    status=status.HTTP_404_NOT_FOUND
                )

        except Exception as e:
            logger.error(f"获取会话命令失败: {e}")
            return Response(
                {'error': str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


# ==================== 新权限系统接口 ====================

from django.contrib.auth.models import Permission
from rest_framework.decorators import api_view, permission_classes

# 权限代码映射表
PERMISSION_CODE_MAPPING = {
    # 仪表盘
    'dashboard.view': 'view_dashboard',

    # CMDB模块
    'cmdb.view': 'view_cmdb',
    'cmdb.hosts.view': 'view_host',
    'cmdb.hosts.add': 'add_host',
    'cmdb.hosts.edit': 'change_host',
    'cmdb.hosts.delete': 'delete_host',
    'cmdb.hostgroups.view': 'view_hostgroup',
    'cmdb.hosttypes.view': 'view_hosttype',

    # Ansible模块
    'ansible.view': 'view_ansibleconfig',
    'ansible.config.view': 'view_ansibleconfig',
    'ansible.inventory.view': 'view_ansibleinventory',
    'ansible.adhoc.view': 'view_adhoc',
    'ansible.playbook.view': 'view_playbook',
    'ansible.credentials.view': 'view_credentials',
    'ansible.monitor.view': 'view_monitor',

    # 审计模块
    'audit.view': 'view_audit',
    'audit.ssh.view': 'view_ssh_audit',
    'audit.ansible.view': 'view_ansible_audit',
    'audit.sql.view': 'view_sql_audit',
    'audit.sessions.view': 'view_sessions',
    'audit.commands.view': 'view_commands',

    # 用户管理模块
    'user.view': 'view_user_module',
    'user.management.view': 'view_user',
    'user.groups.view': 'view_group',
    'user.roles.view': 'view_role',
    'user.permissions.view': 'view_permission',

    # 系统管理模块
    'system.view': 'view_system',
    'system.menu.view': 'view_menuitem',

    # 审计模块
    'audit.view': 'view_audit',

    # 历史记录模块
    'history.view': 'view_history',

    # 监控中心
    'monitoring.view': 'view_monitoring',

    # 测试菜单
    'generated.test.view': 'view_test_menu'
}


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_user_permissions(request):
    """
    获取用户权限列表
    返回用户拥有的所有权限代码
    """
    try:
        user = request.user
        permissions = []

        # 超级管理员拥有所有权限
        if user.is_superuser:
            permissions = ['*']  # 特殊标记表示所有权限
            return Response({
                'status': 'success',
                'permissions': permissions,
                'user_type': 'superuser'
            })

        # 获取用户的所有权限
        user_permissions = set()

        # 1. 通过用户组获取权限
        for group in user.groups.all():
            for perm in group.permissions.all():
                user_permissions.add(f"{perm.content_type.app_label}.{perm.codename}")

        # 2. 通过用户直接权限获取
        for perm in user.user_permissions.all():
            user_permissions.add(f"{perm.content_type.app_label}.{perm.codename}")

        # 3. 通过角色系统获取权限（兼容现有系统）
        try:
            # 获取用户的角色权限
            role_permissions = RoleMenuPermission.objects.filter(
                role__group_roles__group__user=user
            ).select_related('menu_item')

            for role_perm in role_permissions:
                if role_perm.can_read:
                    # 根据菜单路径映射到权限代码
                    menu_path = role_perm.menu_item.path
                    permission_code = _map_path_to_permission_code(menu_path)
                    if permission_code:
                        user_permissions.add(permission_code)
        except Exception as e:
            logger.warning(f"获取角色权限失败: {e}")

        # 4. 将Django权限映射到权限代码
        mapped_permissions = []
        for perm in user_permissions:
            # 查找对应的权限代码
            for code, django_perm in PERMISSION_CODE_MAPPING.items():
                if perm.endswith(django_perm):
                    mapped_permissions.append(code)
                    break

        # 5. 添加基础权限（所有登录用户都有的权限）
        basic_permissions = [
            'dashboard.view',
            'audit.view',
            'history.view',
            'monitoring.view',
            'generated.test.view',
            'system.view',
            'system.menu.view'
        ]
        for basic_perm in basic_permissions:
            if basic_perm not in mapped_permissions:
                mapped_permissions.append(basic_perm)

        logger.info(f"用户 {user.username} 的权限: {mapped_permissions}")

        return Response({
            'status': 'success',
            'permissions': mapped_permissions,
            'user_type': 'normal',
            'debug_info': {
                'django_permissions': list(user_permissions),
                'mapped_count': len(mapped_permissions)
            }
        })

    except Exception as e:
        logger.error(f"获取用户权限失败: {e}")
        return Response({
            'status': 'error',
            'message': str(e),
            'permissions': ['dashboard.view']  # 默认权限
        }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


def _map_path_to_permission_code(path):
    """
    将菜单路径映射到权限代码
    """
    path_mapping = {
        '/dashboard': 'dashboard.view',
        '/cmdb': 'cmdb.view',
        '/cmdb/hosts': 'cmdb.hosts.view',
        '/cmdb/hostgroups': 'cmdb.hostgroups.view',
        '/cmdb/hosttypes': 'cmdb.hosttypes.view',
        '/ansible': 'ansible.view',
        '/ansible/config': 'ansible.config.view',
        '/ansible/inventory': 'ansible.inventory.view',
        '/ansible/adhoc': 'ansible.adhoc.view',
        '/ansible/playbook': 'ansible.playbook.view',
        '/ansible/credentials': 'ansible.credentials.view',
        '/ansible/monitor': 'ansible.monitor.view',
        '/history/ssh-audit': 'audit.ssh.view',
        '/history/ansible-audit': 'audit.ansible.view',
        '/history/sql-audit': 'audit.sql.view',
        '/audit/sessions': 'audit.sessions.view',
        '/audit/commands': 'audit.commands.view',
        '/cmdb/user/management': 'user.management.view',
        '/cmdb/user/groups': 'user.groups.view',
        '/cmdb/user/roles': 'user.roles.view',
        '/cmdb/user/menu-permission': 'user.permissions.view',
        '/system/menu-management': 'system.menu.view',
        '/audit': 'audit.view',
        '/history': 'history.view',
        '/monitoring': 'monitoring.view',
        '/menu': 'generated.test.view'
    }

    return path_mapping.get(path)


class DynamicMenuViewSet(viewsets.ModelViewSet):
    """动态菜单管理视图集"""
    queryset = MenuItem.objects.all()
    serializer_class = DynamicMenuItemSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        """获取菜单查询集"""
        queryset = MenuItem.objects.all()

        # 支持按父菜单过滤
        parent_id = self.request.query_params.get('parent_id')
        if parent_id == 'null' or parent_id == '':
            queryset = queryset.filter(parent__isnull=True)
        elif parent_id:
            queryset = queryset.filter(parent_id=parent_id)

        # 支持搜索
        search = self.request.query_params.get('search')
        if search:
            queryset = queryset.filter(
                Q(name__icontains=search) |
                Q(path__icontains=search) |
                Q(description__icontains=search)
            )

        return queryset.order_by('sort_weight', 'order', 'id')

    @action(detail=False, methods=['get'], url_path='tree')
    def get_menu_tree(self, request):
        """获取菜单树结构"""
        try:
            # 获取所有启用的菜单
            menus = MenuItem.objects.filter(is_enabled=True).order_by('sort_weight', 'order')

            # 构建树结构
            menu_dict = {}
            root_menus = []

            # 先创建所有菜单项的字典
            for menu in menus:
                menu_dict[menu.id] = {
                    'id': menu.id,
                    'name': menu.name,
                    'path': menu.path,
                    'icon': menu.icon,
                    'sort_weight': menu.sort_weight,
                    'component_name': menu.component_name,
                    'is_generated': menu.is_generated,
                    'permission_code': menu.permission_code,
                    'description': menu.description,
                    'parent_id': menu.parent_id,
                    'created_at': menu.created_at.isoformat() if menu.created_at else None,
                    'updated_at': menu.updated_at.isoformat() if menu.updated_at else None,
                    'children': []
                }

            # 构建父子关系
            for menu in menus:
                menu_item = menu_dict[menu.id]
                if menu.parent_id and menu.parent_id in menu_dict:
                    menu_dict[menu.parent_id]['children'].append(menu_item)
                else:
                    root_menus.append(menu_item)

            return Response({
                'success': True,
                'data': root_menus
            })

        except Exception as e:
            logger.error(f"获取菜单树失败: {e}")
            return Response({
                'success': False,
                'message': f'获取菜单树失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['post'], url_path='create-menu')
    def create_menu(self, request):
        """创建新菜单"""
        try:
            serializer = MenuCreateSerializer(data=request.data)
            if serializer.is_valid():
                # 获取generate_page参数
                generate_page = request.data.get('generate_page', True)

                # 创建菜单项
                menu_item = serializer.save()

                # 根据用户选择决定是否生成占位页面
                if generate_page and menu_item.is_generated:
                    from .utils.menu_generator import MenuGenerator
                    generator = MenuGenerator()

                    try:
                        # 传递菜单数据给create_component_file，它会自动使用英文文件名
                        file_path = generator.create_component_file({
                            'name': menu_item.name,
                            'icon': menu_item.icon,
                            'description': menu_item.description,
                            'component_name': menu_item.component_name
                        })
                        logger.info(f"成功创建占位页面: {file_path}")
                    except Exception as e:
                        logger.warning(f"创建占位页面失败: {e}")

                # 返回创建的菜单数据
                response_serializer = DynamicMenuItemSerializer(menu_item)
                return Response({
                    'success': True,
                    'message': f'菜单 "{menu_item.name}" 创建成功' + ('，页面文件已生成' if generate_page and menu_item.is_generated else ''),
                    'data': response_serializer.data
                }, status=status.HTTP_201_CREATED)
            else:
                return Response({
                    'success': False,
                    'message': '数据验证失败',
                    'errors': serializer.errors
                }, status=status.HTTP_400_BAD_REQUEST)

        except Exception as e:
            logger.error(f"创建菜单失败: {e}")
            return Response({
                'success': False,
                'message': f'创建菜单失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['post'], url_path='reorder')
    def reorder_menus(self, request):
        """重新排序菜单"""
        try:
            menu_orders = request.data.get('menu_orders', [])

            if not menu_orders:
                return Response({
                    'success': False,
                    'message': '菜单排序数据不能为空'
                }, status=status.HTTP_400_BAD_REQUEST)

            # 批量更新排序权重
            updated_count = 0
            for item in menu_orders:
                menu_id = item.get('id')
                sort_weight = item.get('sort_weight')
                parent_id = item.get('parent_id')

                if menu_id and sort_weight is not None:
                    try:
                        menu = MenuItem.objects.get(id=menu_id)
                        menu.sort_weight = sort_weight
                        menu.parent_id = parent_id if parent_id else None
                        menu.save()
                        updated_count += 1
                    except MenuItem.DoesNotExist:
                        logger.warning(f"菜单 ID {menu_id} 不存在")
                        continue

            return Response({
                'success': True,
                'message': f'成功更新 {updated_count} 个菜单的排序',
                'updated_count': updated_count
            })

        except Exception as e:
            logger.error(f"菜单排序失败: {e}")
            return Response({
                'success': False,
                'message': f'菜单排序失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['get'], url_path='icons')
    def get_available_icons(self, request):
        """获取可用的图标列表"""
        # FontAwesome 图标列表
        icons = [
            'fa-dashboard', 'fa-server', 'fa-users', 'fa-cogs', 'fa-terminal',
            'fa-database', 'fa-chart-bar', 'fa-file', 'fa-folder', 'fa-home',
            'fa-user', 'fa-group', 'fa-key', 'fa-lock', 'fa-unlock',
            'fa-settings', 'fa-gear', 'fa-wrench', 'fa-tools', 'fa-edit',
            'fa-plus', 'fa-minus', 'fa-times', 'fa-check', 'fa-save',
            'fa-download', 'fa-upload', 'fa-cloud', 'fa-network-wired',
            'fa-wifi', 'fa-globe', 'fa-map', 'fa-location', 'fa-search',
            'fa-filter', 'fa-sort', 'fa-list', 'fa-table', 'fa-th',
            'fa-calendar', 'fa-clock', 'fa-history', 'fa-refresh',
            'fa-sync', 'fa-play', 'fa-pause', 'fa-stop', 'fa-forward',
            'fa-backward', 'fa-volume-up', 'fa-volume-down', 'fa-mute',
            'fa-bell', 'fa-envelope', 'fa-phone', 'fa-mobile',
            'fa-laptop', 'fa-desktop', 'fa-tablet', 'fa-gamepad',
            'fa-camera', 'fa-video', 'fa-image', 'fa-music',
            'fa-book', 'fa-bookmark', 'fa-tag', 'fa-tags',
            'fa-star', 'fa-heart', 'fa-thumbs-up', 'fa-thumbs-down',
            'fa-warning', 'fa-info', 'fa-question', 'fa-exclamation',
            'fa-shield', 'fa-bug', 'fa-code', 'fa-html5',
            'fa-css3', 'fa-js', 'fa-python', 'fa-java',
            'fa-docker', 'fa-git', 'fa-github', 'fa-gitlab',
            'fa-linux', 'fa-windows', 'fa-apple', 'fa-android'
        ]

        return Response({
            'success': True,
            'data': icons
        })

    @action(detail=True, methods=['delete'], url_path='delete-with-component')
    def delete_with_component(self, request, pk=None):
        """删除菜单及其组件文件"""
        try:
            menu_item = self.get_object()
            component_name = menu_item.component_name

            # 删除组件文件
            if component_name and menu_item.is_generated:
                try:
                    import os
                    from django.conf import settings

                    frontend_path = os.path.join(settings.BASE_DIR, '..', 'devops-frontend')
                    component_file = os.path.join(frontend_path, 'src', 'views', 'generated', f'{component_name}.vue')

                    if os.path.exists(component_file):
                        os.remove(component_file)
                        logger.info(f"已删除组件文件: {component_file}")
                except Exception as e:
                    logger.warning(f"删除组件文件失败: {e}")

            # 删除菜单项
            menu_name = menu_item.name
            menu_item.delete()

            return Response({
                'success': True,
                'message': f'菜单 "{menu_name}" 及其相关文件已删除'
            })

        except Exception as e:
            logger.error(f"删除菜单失败: {e}")
            return Response({
                'success': False,
                'message': f'删除菜单失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['post'], url_path='fix-paths')
    def fix_menu_paths(self, request):
        """修复菜单路径，将中文路径转换为英文路径"""
        try:
            from .utils.menu_generator import MenuGenerator

            generator = MenuGenerator()
            updated_count = 0

            # 获取所有菜单
            menus = MenuItem.objects.all()

            for menu in menus:
                # 生成新的英文路径
                new_path = generator.generate_route_path(menu.name)

                # 如果路径不同，则更新
                if menu.path != f'/{new_path}':
                    old_path = menu.path
                    menu.path = f'/{new_path}'
                    menu.save()
                    updated_count += 1
                    logger.info(f"更新菜单路径: {menu.name} {old_path} -> {menu.path}")

            return Response({
                'success': True,
                'message': f'成功修复 {updated_count} 个菜单的路径',
                'data': {
                    'updated_count': updated_count,
                    'total_count': menus.count()
                }
            })

        except Exception as e:
            logger.error(f"修复菜单路径失败: {e}")
            return Response({
                'success': False,
                'message': f'修复菜单路径失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['post'], url_path='sync_static_config')
    def sync_static_config(self, request):
        """同步静态配置文件"""
        try:
            from .utils.config_sync import ConfigSyncManager

            sync_manager = ConfigSyncManager()
            result = sync_manager.sync_static_menu_config()

            if result['success']:
                return Response({
                    'success': True,
                    'message': '静态配置文件同步成功',
                    'data': result
                })
            else:
                return Response({
                    'success': False,
                    'message': f'静态配置文件同步失败: {result.get("error", "未知错误")}'
                }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        except Exception as e:
            logger.error(f"同步静态配置失败: {e}")
            return Response({
                'success': False,
                'message': f'同步静态配置失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['post'], url_path='sync_route_config')
    def sync_route_config(self, request):
        """同步路由配置"""
        try:
            from .utils.config_sync import ConfigSyncManager

            sync_manager = ConfigSyncManager()
            result = sync_manager.sync_route_config()

            if result['success']:
                return Response({
                    'success': True,
                    'message': '路由配置同步成功',
                    'data': result
                })
            else:
                return Response({
                    'success': False,
                    'message': f'路由配置同步失败: {result.get("error", "未知错误")}'
                }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        except Exception as e:
            logger.error(f"同步路由配置失败: {e}")
            return Response({
                'success': False,
                'message': f'同步路由配置失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['post'])
    def full_sync(self, request):
        """完整同步（静态配置 + 路由 + 权限）"""
        try:
            # 获取所有启用的菜单
            menus = MenuItem.objects.filter(is_enabled=True).order_by('sort_weight', 'order')

            # 构建菜单树结构
            menu_tree = self._build_menu_tree(menus)

            # 生成新的静态菜单配置
            config_content = self._generate_static_menu_config(menu_tree)

            # 写入前端配置文件
            import os
            frontend_config_path = '/code/devops/devops-frontend/src/config/menus.ts'
            router_config_path = '/code/devops/devops-frontend/src/router/dynamicRoutes.ts'

            try:
                # 1. 写入菜单配置文件
                with open(frontend_config_path, 'w', encoding='utf-8') as f:
                    f.write(config_content)

                # 2. 生成并写入动态路由配置
                router_content = self._generate_dynamic_routes(menus)
                os.makedirs(os.path.dirname(router_config_path), exist_ok=True)
                with open(router_config_path, 'w', encoding='utf-8') as f:
                    f.write(router_content)

                return Response({
                    'success': True,
                    'message': '菜单配置和路由已同步，请刷新页面查看最新菜单',
                    'data': {
                        'menu_count': len(menu_tree),
                        'config_path': frontend_config_path,
                        'router_path': router_config_path,
                        'timestamp': timezone.now().isoformat()
                    }
                })

            except Exception as file_error:
                logger.error(f"写入配置文件失败: {file_error}")
                return Response({
                    'success': False,
                    'message': f'写入配置文件失败: {str(file_error)}'
                }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        except Exception as e:
            logger.error(f"完整同步失败: {e}")
            return Response({
                'success': False,
                'message': f'完整同步失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    def _build_menu_tree(self, menus):
        """构建菜单树结构"""
        menu_dict = {}
        root_menus = []

        # 先创建所有菜单项的字典
        for menu in menus:
            menu_dict[menu.id] = {
                'id': menu.id,
                'name': menu.name,
                'path': menu.path,
                'icon': menu.icon,
                'order': int(menu.sort_weight or menu.order or 0),
                'permission_code': menu.permission_code or f'{menu.name.lower()}.view',
                'parent_id': menu.parent_id,
                'children': []
            }

        # 构建父子关系
        for menu in menus:
            menu_item = menu_dict[menu.id]
            if menu.parent_id and menu.parent_id in menu_dict:
                menu_dict[menu.parent_id]['children'].append(menu_item)
            else:
                root_menus.append(menu_item)

        # 排序
        root_menus.sort(key=lambda x: x['order'])
        for menu in root_menus:
            if menu['children']:
                menu['children'].sort(key=lambda x: x['order'])

        return root_menus

    def _generate_static_menu_config(self, menu_tree):
        """生成静态菜单配置文件内容"""

        def format_menu_item(item, indent=0):
            spaces = '  ' * indent
            children_str = ''

            if item['children']:
                children_content = ',\n'.join([
                    format_menu_item(child, indent + 2)
                    for child in item['children']
                ])
                children_str = f",\n{spaces}  children: [\n{children_content}\n{spaces}  ]"
            else:
                children_str = f",\n{spaces}  children: []"

            return f"""{spaces}{{
{spaces}  id: '{item['id']}',
{spaces}  name: '{item['name']}',
{spaces}  path: '{item['path']}',
{spaces}  icon: '{item['icon']}',
{spaces}  order: {item['order']},
{spaces}  permission_code: '{item['permission_code']}'{children_str}
{spaces}}}"""

        menu_items = ',\n'.join([format_menu_item(item, 1) for item in menu_tree])

        # 生成权限代码映射
        permission_codes = []
        def extract_permissions(items):
            for item in items:
                if item.get('permission_code'):
                    permission_codes.append(f"  '{item['permission_code']}': '{item['name']}'")
                if item.get('children'):
                    extract_permissions(item['children'])

        extract_permissions(menu_tree)
        permission_mapping = ',\n'.join(permission_codes)

        return f"""// 自动生成的菜单配置文件
// 生成时间: {timezone.now().isoformat()}
// 此文件由动态菜单系统自动生成，请勿手动修改

export interface MenuItem {{
  id: string | number
  name: string
  path: string
  icon: string
  order: number
  permission_code?: string
  children?: MenuItem[]
}}

export const STATIC_MENUS: MenuItem[] = [
{menu_items}
]

// 权限代码映射
export const PERMISSION_CODES = {{
{permission_mapping}
}}

/**
 * 根据用户权限过滤菜单
 * @param userPermissions 用户权限列表
 * @returns 过滤后的菜单列表
 */
export function getFilteredMenus(userPermissions: string[]): MenuItem[] {{
  // 如果没有权限，返回空数组
  if (!userPermissions || userPermissions.length === 0) {{
    return []
  }}

  // 递归过滤菜单的函数
  function filterMenuItems(menus: MenuItem[]): MenuItem[] {{
    return menus.filter(menu => {{
      // 检查当前菜单是否有权限
      const hasPermission = !menu.permission_code ||
                           userPermissions.includes(menu.permission_code) ||
                           userPermissions.includes('*') ||
                           userPermissions.includes('admin')

      // 如果有子菜单，递归过滤子菜单
      if (menu.children && menu.children.length > 0) {{
        const filteredChildren = filterMenuItems(menu.children)

        // 如果当前菜单有权限，或者有可访问的子菜单，则保留
        if (hasPermission || filteredChildren.length > 0) {{
          return {{
            ...menu,
            children: filteredChildren
          }}
        }}
        return false
      }}

      // 叶子节点，直接根据权限判断
      return hasPermission
    }}).map(menu => {{
      // 确保返回的菜单有正确的children属性
      if (menu.children && menu.children.length > 0) {{
        return {{
          ...menu,
          children: filterMenuItems(menu.children)
        }}
      }}
      return menu
    }})
  }}

  // 过滤并排序菜单
  const filteredMenus = filterMenuItems(STATIC_MENUS)

  // 按order字段排序
  return filteredMenus.sort((a, b) => (a.order || 0) - (b.order || 0))
}}

export default STATIC_MENUS
"""

    def _generate_dynamic_routes(self, menus):
        """生成动态路由配置文件内容"""
        from .utils.menu_generator import MenuGenerator

        generator = MenuGenerator()

        # 获取所有需要生成路由的菜单
        route_menus = []
        for menu in menus:
            if menu.is_generated and menu.component_name and menu.path:
                # 使用MenuGenerator生成英文路由名称和文件名
                route_name = generator.generate_route_name(menu.name)
                route_menus.append({
                    'path': menu.path.lstrip('/'),  # 移除开头的斜杠
                    'name': route_name,  # 使用英文路由名称
                    'component_file': route_name,  # 使用英文文件名
                    'title': menu.name,
                    'icon': menu.icon
                })

        # 生成路由配置
        route_items = []
        for route in route_menus:
            route_items.append(f"""  {{
    path: '{route['path']}',
    name: '{route['name']}',
    component: () => import('@/views/generated/{route['component_file']}.vue'),
    meta: {{ title: '{route['title']}', icon: '{route['icon']}' }}
  }}""")

        routes_content = ',\n'.join(route_items)

        return f"""// 自动生成的动态路由配置文件
// 生成时间: {timezone.now().isoformat()}
// 此文件由动态菜单系统自动生成，请勿手动修改

import type {{ RouteRecordRaw }} from 'vue-router'

export const dynamicRoutes: RouteRecordRaw[] = [
{routes_content}
]

export default dynamicRoutes
"""


class HostsAndGroupsView(APIView):
    """
    从指定的Ansible Inventory中解析主机和主机组，用于智能提示
    """
    permission_classes = [permissions.IsAuthenticated]

    def get(self, request):
        """解析Inventory内容，获取主机和主机组数据"""
        try:
            # 获取inventory_id参数
            inventory_id = request.query_params.get('inventory_id')
            if not inventory_id:
                return Response({
                    'status': 'error',
                    'message': '请提供inventory_id参数',
                    'data': {
                        'hosts': [],
                        'groups': []
                    }
                }, status=status.HTTP_400_BAD_REQUEST)

            # 从ansible_management应用获取inventory
            from ansible_management.models import AnsibleInventory
            try:
                inventory = AnsibleInventory.objects.get(id=inventory_id)
            except AnsibleInventory.DoesNotExist:
                return Response({
                    'status': 'error',
                    'message': f'Inventory {inventory_id} 不存在',
                    'data': {
                        'hosts': [],
                        'groups': []
                    }
                }, status=status.HTTP_404_NOT_FOUND)

            # 解析inventory内容
            hosts_data = []
            groups_data = []

            if inventory.content:
                hosts_set = set()
                groups_set = set()

                lines = inventory.content.split('\n')
                current_group = None

                for line in lines:
                    line = line.strip()
                    if not line or line.startswith('#'):
                        continue

                    # 检查是否是组定义 [groupname]
                    if line.startswith('[') and line.endswith(']'):
                        group_name = line[1:-1]
                        # 排除特殊组（如 [groupname:vars] 或 [groupname:children]）
                        if ':' not in group_name:
                            current_group = group_name
                            groups_set.add(group_name)
                        else:
                            current_group = None
                        continue

                    # 解析主机行
                    if current_group is not None or current_group == '':
                        # 提取主机名（可能包含变量）
                        # 格式: hostname ansible_host=ip ansible_user=user 等
                        host_parts = line.split()
                        if host_parts:
                            hostname = host_parts[0]
                            # 排除变量定义行
                            if '=' not in hostname:
                                hosts_set.add(hostname)
                            elif hostname and not hostname.startswith('['):
                                # 处理 hostname=value 格式
                                if '=' in hostname:
                                    actual_hostname = hostname.split('=')[0]
                                    if actual_hostname:
                                        hosts_set.add(actual_hostname)
                                else:
                                    hosts_set.add(hostname)

                # 转换为列表格式
                hosts_data = [{'name': host, 'hostname': host} for host in sorted(hosts_set)]
                groups_data = [{'name': group, 'host_count': 0} for group in sorted(groups_set)]

                # 计算每个组的主机数量（简化版本）
                for group_data in groups_data:
                    group_name = group_data['name']
                    # 这里可以进一步解析每个组包含的主机数量
                    # 暂时设为0，因为需要更复杂的解析逻辑
                    group_data['host_count'] = len([h for h in hosts_set])  # 简化处理

            return Response({
                'status': 'success',
                'data': {
                    'hosts': hosts_data,
                    'groups': groups_data
                },
                'inventory_name': inventory.name
            })

        except Exception as e:
            logger.error(f"解析Inventory失败: {str(e)}")
            return Response({
                'status': 'error',
                'message': f'解析Inventory失败: {str(e)}',
                'data': {
                    'hosts': [],
                    'groups': []
                }
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
